forked from RIOT-OS/RIOT
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request RIOT-OS#12550 from aabadie/pr/sys/progress_bar
sys/progress_bar: add module for managing a progress bar in stdout
- Loading branch information
Showing
7 changed files
with
381 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include $(RIOTBASE)/Makefile.base |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |