Home

Awesome

<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="docs/img/barkeep-logo-white.png" width="300"> <source media="(prefers-color-scheme: light)" srcset="docs/img/barkeep-logo-black.png" width="300"> <img src="docs/img/barkeep-logo-black.png" width="300"> </picture> </p>

Small, single C++ header to display async animations, counters, and progress bars. Use it by including barkeep.h in your project. barkeep strives to be non-intrusive. barkeep also has python bindings.

<div> <picture> <source media="(prefers-color-scheme: dark)" srcset="docs/img/C++-light.svg" height="22"> <source media="(prefers-color-scheme: light)" srcset="docs/img/C++-dark.svg" height="22"> <img src="docs/img/C++-dark.svg" height="22"> </picture> <a href="https://github.com/oir/barkeep/actions/workflows/build-test.yml/badge.svg"><img src="https://github.com/oir/barkeep/actions/workflows/build-test.yml/badge.svg" alt="Build status"></a> <a href="https://coveralls.io/github/oir/barkeep?branch=main"><img src="https://coveralls.io/repos/github/oir/barkeep/badge.svg?branch=main" alt="Coverage status"></a> <a href=""><img src="https://img.shields.io/badge/std-c++20-blue.svg" alt="c++20"></a> <a href=""><img src="docs/img/C++-include.svg"></a> </div> <div> <picture> <source media="(prefers-color-scheme: dark)" srcset="docs/img/python-light.svg" height="22"> <source media="(prefers-color-scheme: light)" srcset="docs/img/python-dark.svg" height="22"> <img src="docs/img/python-dark.svg" height="22"> </picture> <a href="https://github.com/oir/barkeep/actions/workflows/build-wheels.yml/badge.svg"><img src="https://github.com/oir/barkeep/actions/workflows/build-wheels.yml/badge.svg" alt="Build status"></a> <a href="https://pypi.python.org/pypi/barkeep"><img src="https://img.shields.io/badge/python-3.9_|_3.10_|_3.11_|_3.12-blue.svg" alt="pypi"></a> <a href=""><img src="docs/img/pip-install.svg"></a> </div>

See demo.cpp for more examples.

Non intrusive design

Usually when you get to a point where you think you might want a waiting animation, you probably already have some variables you are monitoring and maybe even occasionally printing to screen. Displaying an animation comes as an afterthought.

barkeep strives to be minimally intrusive by monitoring existing variables using pointers, so that in such situations you can start using it with very little code change.

<table> <tr> </tr> <tr> <td>

Before

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>





// tokenize by space
std::vector<std::string> tknz(std::string s) {
  std::vector<std::string> rval;
  std::istringstream iss(s);
  for (std::string word; iss >> word;) {
    rval.push_back(word);
  }
  return rval;
}

void process_document(const std::string& doc,
                      std::ofstream& out,
                      size_t& total_chars,
                      size_t& total_tokens) {
  auto tokens = tknz(doc);
  for (auto& token : tokens) {
     out << token << std::endl;
     total_chars += token.size();
     total_tokens++;
  }
  out << std::endl;
}

int main(int /*argc*/, char** /*argv*/) {
  std::vector<std::string> docs = {/*...*/};
  std::ofstream out("tokens.txt");
  size_t chars = 0, tokens = 0;




  
  for (size_t i = 0; i < docs.size(); ++i) {
    std::cout << "Doc " << i << std::endl;
    process_document(docs[i], out,
                     chars, tokens);
  }


  std::cout << "Total: " << chars
            << tokens << std::endl;

  return 0;
}
</td> <td>

After

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include <barkeep/barkeep.h>

namespace bk = barkeep;

// tokenize by space
std::vector<std::string> tknz(std::string s) {
  std::vector<std::string> rval;
  std::istringstream iss(s);
  for (std::string word; iss >> word;) {
    rval.push_back(word);
  }
  return rval;
}

void process_document(const std::string& doc,
                      std::ofstream& out,
                      size_t& total_chars,
                      size_t& total_tokens) {
  auto tokens = tknz(doc);
  for (auto& token : tokens) {
     out << token << std::endl;
     total_chars += token.size();
     total_tokens++;
  }
  out << std::endl;
}

int main(int /*argc*/, char** /*argv*/) {
  std::vector<std::string> docs = {/*...*/};
  std::ofstream out("tokens.txt");
  size_t chars = 0, tokens = 0, i = 0;

  auto bar = bk::ProgressBar(&i, {.total=docs.size(), .show=false}) |
             bk::Counter(&tokens, {.message="Tokens", .show=false}) |
             bk::Counter(&chars, {.message="Chars", .show=false});
  bar->show();
  for (i = 0; i < docs.size(); ++i) {

    process_document(docs[i], out,
                     chars, tokens);
  }
  bar->done();

  std::cout << "Total: " << chars
            << tokens << std::endl;

  return 0;
}
</td> </tr> </table>

In the example above, we add a display to monitor the loop variable i, total_chars, and total_tokens. For-loop changes slightly (because i needs to be declared earlier), but the way in which these variables are used in code stays the same. For instance, we do not use a custom data structure to call operator++() to increment progress. As a result, signature of process_document() does not change.

We start and stop the display and barkeep is out of the way.

Caveat

Since displaying thread typically works concurrently, reads of progress variables (i, total_chars, total_tokens) is always racing with your own modifications. Even though theoretically it is possible that a read can interleave a write in the middle such that you read e.g. a 4 byte float where 2 byte of is fresh and 2 byte is stale, this kind of concurrent access seems to be almost always okay in practice (see, e.g. this, and this thread). It has always been okay in my own anecdotal experience. If not, a race condition would result in momentarily displaying a garbage value.

Given the practical rarity of encountering this, its minimal impact outcome, and the desire to be as non-intrusive as possible, barkeep does not introduce any lock guards (which would require a custom type as the progress variables instead of, e.g. an int or float).

If you still want to be extra safe and guarantee non-racing read and writes, you can use std::atomic<T> for your progress variables, as can be seen in some of the examples above.

Advanced formatting

You can enable advanced formatting by either

Unlike fmt::format, std::format does not support named arguments, which is a limitation you might consider. Thus, std::format requires to use integer identifiers to refer to bar components as you will see below.

In either of these cases, Counters and ProgressBars have an additional Config option "format". This option can be used to format the entire display using a fmt-like format string instead of using textual options like message or speed_unit:

When format is used, other textual parameters, such as message or speed_unit are ignored.

Additionally, some basic ansi color sequences are predefined as identifiers which could be used to add color:

<picture> <source media="(prefers-color-scheme: dark)" srcset="docs/rec/fmt-color-dark.svg" width="700"> <source media="(prefers-color-scheme: light)" srcset="docs/rec/fmt-color-light.svg" width="700"> <img src="docs/rec/fmt-color-light.svg" width="700"> </picture>

See demo-fmtlib.cpp or demo-stdfmt.cpp for more examples.

Notes

Building

barkeep is header only, so you can simply include the header in your C++ project. Still, this section details how to build the demos, tests and python bindings and can be used for reference.

No tooling

If you don't want to deal with even a Makefile, you can simply invoke the compiler on the corresponding .cpp files.

Detail: Github submodules are staged in folders that end with a _ to avoid clashing with Meson's subproject downloading.

Python bindings are slightly more involved, therefore a proper build system is recommended, see below.

Minimal tooling: Make

If you don't want to deal with a complex build system, but also don't want to invoke raw compiler commands, you can use make.

Clone the repo with submodules as in the previous section and cd into it.

Build demo and tests:

make all

...and run:

./demo.out
./test.out
./test-stdfmt.out
./test-fmtlib.out

Python bindings are slightly more involved, therefore a proper build system is recommended, see below.

Build system: Meson

Meson has its own subproject staging logic, thus cloning the submodules is not needed.

Similar projects