Skip to content

Commit

Permalink
Merge pull request RIOT-OS#12550 from aabadie/pr/sys/progress_bar
Browse files Browse the repository at this point in the history
sys/progress_bar: add module for managing a progress bar in stdout
  • Loading branch information
kaspar030 authored Dec 5, 2019
2 parents 7ffc30a + 3f1ac65 commit 8ae7201
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 0 deletions.
132 changes: 132 additions & 0 deletions sys/include/progress_bar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2019 Inria
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @ingroup sys_progress_bar
* @{
*
* @file
* @brief A simple CLI progress bar
*
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
*/

#ifndef PROGRESS_BAR_H
#define PROGRESS_BAR_H

#include <stdlib.h>
#include <inttypes.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Progress bar maximum characters length
*/
#ifndef PROGRESS_BAR_LENGTH
#define PROGRESS_BAR_LENGTH (25U)
#endif

/**
* @brief Progress bar character
*/
#ifndef PROGRESS_BAR_FULL_CHARACTER
#define PROGRESS_BAR_FULL_CHARACTER "█"
#endif

/**
* @brief Progress bar empty character
*/
#ifndef PROGRESS_BAR_EMPTY_CHARACTER
#define PROGRESS_BAR_EMPTY_CHARACTER " "
#endif

/**
* @brief Character displayed on the left of the progress bar
*/
#ifndef PROGRESS_BAR_PREFIX_CHARACTER
#define PROGRESS_BAR_PREFIX_CHARACTER "|"
#endif

/**
* @brief Character displayed on the left of the progress bar
*/
#ifndef PROGRESS_BAR_SUFFIX_CHARACTER
#define PROGRESS_BAR_SUFFIX_CHARACTER "|"
#endif

/**
* @brief Progress bar prefix max length
*/
#ifndef PROGRESS_BAR_PREFIX_MAX_LENGTH
#define PROGRESS_BAR_PREFIX_MAX_LENGTH (32U)
#endif

/**
* @brief Progress bar suffix max length
*/
#ifndef PROGRESS_BAR_SUFFIX_MAX_LENGTH
#define PROGRESS_BAR_SUFFIX_MAX_LENGTH (32U)
#endif

/**
* @brief Progress bar descriptor
*/
typedef struct {
/** Current value of the progress bar. Must be between 0 and 100 (included) */
uint8_t value;
/** Prefix displayed on the left of the progress bar */
char prefix[PROGRESS_BAR_PREFIX_MAX_LENGTH];
/** Suffix displayed on the right of the progress bar */
char suffix[PROGRESS_BAR_SUFFIX_MAX_LENGTH];
} progress_bar_t;

/**
* @brief Print a progress bar in the terminal
*
* @param[in] prefix String displayed on the left of the progress bar
* @param[in] suffix String displayed on the right of the progress bar
* @param[in] value Value of the progress bar
*/
void progress_bar_print(char *prefix, char *suffix, uint8_t value);

/**
* @brief Update the progress bar display in the terminal
*
* @param[in] progress_bar Pointer to the progress bar descriptor
*/
void progress_bar_update(progress_bar_t *progress_bar);

/**
* @brief Prepare the output for displaying multiple progress bars.
*
* This function is just adding enough empty lines to give enough space to
* print the list of progress bars.
*
* This function must be called only once and before starting the progress bar
* list updates with.
*
* @param[in] len The length of the progress bar array
*/
void progress_bar_prepare_multi(uint8_t len);

/**
* @brief Update all progress bar displays of the given progress bars list
*
* @param[in] progress_bar_list An array of progress bars
* @param[in] len The length of the progress bar array
*/
void progress_bar_update_multi(progress_bar_t *progress_bar_list, uint8_t len);

#ifdef __cplusplus
}
#endif

/** @} */
#endif /* PROGRESS_BAR_H */
1 change: 1 addition & 0 deletions sys/progress_bar/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base
5 changes: 5 additions & 0 deletions sys/progress_bar/doc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* @defgroup sys_progress_bar A terminal progress bar
* @ingroup sys
* @brief Manage a progress bar on the standard output
*/
106 changes: 106 additions & 0 deletions sys/progress_bar/progress_bar.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2019 Inria
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @ingroup sys_progress_bar
* @{
*
* @file
* @brief Progress bar implementation
*
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
*
* @}
*/

#include <stdio.h>
#include <inttypes.h>
#include <assert.h>

#include "progress_bar.h"

void progress_bar_print(char *prefix, char *suffix, uint8_t value)
{
if (value > 100) {
value = 100;
}

/* Hide cursor */
printf("\033[?25l");

/* Hack for pyterm: prepare space for the progress bar */
putchar('\n');
printf("\033[1A");

/* Move cursor at the beginning of the line */
putchar('\r');

/* Display progress bar prefix if any */
if (prefix) {
printf("%s", prefix);
}

printf(PROGRESS_BAR_PREFIX_CHARACTER);

/* Fully reprint the progress bar */
for (unsigned i = 0; i < PROGRESS_BAR_LENGTH; ++i) {
if (100 * i < (uint16_t)(value * PROGRESS_BAR_LENGTH)) {
printf(PROGRESS_BAR_FULL_CHARACTER);
}
else {
printf(PROGRESS_BAR_EMPTY_CHARACTER);
}
}

printf(PROGRESS_BAR_SUFFIX_CHARACTER);

/* Display progress bar suffix if any */
if (suffix) {
printf("%s", suffix);
}

/* Hack for pyterm */
printf("\033[s");
putchar('\n');
printf("\033[u");

/* show cursor */
printf("\033[?25h");

#ifdef MODULE_NEWLIB
fflush(stdout);
#endif
}

void progress_bar_update(progress_bar_t *progress_bar)
{
progress_bar_print(progress_bar->prefix, progress_bar->suffix,
progress_bar->value);
}

void progress_bar_prepare_multi(uint8_t len)
{
/* Give enough space to print all progress bars. */
for (uint8_t i = 0; i < len; ++i) {
putchar('\n');
}
}

void progress_bar_update_multi(progress_bar_t *progress_bar_list, uint8_t len)
{
/* Move cursor to the line of the first progress bar. */
printf("\033[%dA", len);

for (uint8_t i = 0; i < len; ++i) {
/* Display each progress bar as usual */
progress_bar_update(&progress_bar_list[i]);

/* Move cursor to next progress bar line. */
putchar('\n');
}
}
26 changes: 26 additions & 0 deletions tests/progress_bar/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
include ../Makefile.tests_common

USEMODULE += xtimer
USEMODULE += progress_bar

PROGRESS_BAR_LENGTH ?= 50
PROGRESS_BAR_FULL_CHARACTER ?= "█"
PROGRESS_BAR_EMPTY_CHARACTER ?= " "

# Other nice progress bar characters:
#PROGRESS_BAR_FULL_CHARACTER ?= "◉"
#PROGRESS_BAR_EMPTY_CHARACTER ?= "◯"
#PROGRESS_BAR_FULL_CHARACTER ?= "▣"
#PROGRESS_BAR_EMPTY_CHARACTER ?= "▢"

CFLAGS += -DPROGRESS_BAR_FULL_CHARACTER=\"$(PROGRESS_BAR_FULL_CHARACTER)\"
CFLAGS += -DPROGRESS_BAR_EMPTY_CHARACTER=\"$(PROGRESS_BAR_EMPTY_CHARACTER)\"
CFLAGS += -DPROGRESS_BAR_LENGTH=$(PROGRESS_BAR_LENGTH)

include $(RIOTBASE)/Makefile.include

# Make custom progress bar characters available in Python test script via
# environment variables
export PROGRESS_BAR_FULL_CHARACTER
export PROGRESS_BAR_EMPTY_CHARACTER
export PROGRESS_BAR_LENGTH
73 changes: 73 additions & 0 deletions tests/progress_bar/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2019 Inria
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @ingroup tests
* @{
*
* @file
* @brief progress_bar test application
*
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
*
* @}
*/

#include <stdio.h>
#include <string.h>

#include "xtimer.h"

#include "progress_bar.h"

#define PROGRESS_BAR_LIST_NUMOF (5U)

/* Test single progress bar */
static progress_bar_t progress_bar;

/* Test multiple progress bars */
static progress_bar_t progress_bar_list[PROGRESS_BAR_LIST_NUMOF];

int main(void)
{
puts("Progress bar test application.");

/* Test a single progress bar */
sprintf(progress_bar.prefix, "%s ", "Progress bar 0");

for (uint8_t i = 0; i < 101; ++i) {
progress_bar.value = i;
sprintf(progress_bar.suffix, " %3d%%", i);

progress_bar_update(&progress_bar);

xtimer_usleep(50 * US_PER_MS);
}

puts("\nDone!");

/* Prepare enough space for the progress bars */
progress_bar_prepare_multi(PROGRESS_BAR_LIST_NUMOF);

for (uint8_t i = 0; i < PROGRESS_BAR_LIST_NUMOF; ++i) {
sprintf(progress_bar_list[i].prefix, "%s %d ", "Progress bar", i + 1);
}

for (uint8_t i = 0; i < 101; ++i) {
for (uint8_t p = PROGRESS_BAR_LIST_NUMOF; p > 0 ; --p) {
(p * i < 101) ? progress_bar_list[PROGRESS_BAR_LIST_NUMOF - p].value = i * p : 100;
}

progress_bar_update_multi(progress_bar_list, PROGRESS_BAR_LIST_NUMOF);
xtimer_usleep(50 * US_PER_MS);
}

puts("Done!");

return 0;
}
38 changes: 38 additions & 0 deletions tests/progress_bar/tests/01-run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3

# Copyright (C) 2019 Inria
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.

import os
import sys
from testrunner import run


TIMEOUT = 60
LENGTH = int(os.getenv('PROGRESS_BAR_LENGTH'))
FULL_CHARACTER = os.getenv('PROGRESS_BAR_FULL_CHARACTER')[1:-1]
EMPTY_CHARACTER = os.getenv('PROGRESS_BAR_EMPTY_CHARACTER')[1:-1]


def testfunc(child):
for i in range(0, 100, 10):
ratio = int(i * LENGTH / 100.0)
progress_str = FULL_CHARACTER * ratio
progress_str += EMPTY_CHARACTER * (LENGTH - ratio)
check_str = 'Progress bar 0 |{}| {:3}%'.format(
progress_str, i)
child.expect_exact(check_str)
child.expect_exact("Done!")

for i in range(2, 6): # 5 parallel progress bars
check_str = 'Progress bar {} |{}|'.format(
i, LENGTH * FULL_CHARACTER)
child.expect_exact(check_str, timeout=TIMEOUT)
child.expect_exact('Done!')


if __name__ == "__main__":
sys.exit(run(testfunc))

0 comments on commit 8ae7201

Please sign in to comment.