mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 12:52:44 +01:00
Merge pull request #12550 from aabadie/pr/sys/progress_bar
sys/progress_bar: add module for managing a progress bar in stdout
This commit is contained in:
commit
8ae7201962
132
sys/include/progress_bar.h
Normal file
132
sys/include/progress_bar.h
Normal file
@ -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
sys/progress_bar/Makefile
Normal file
1
sys/progress_bar/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
5
sys/progress_bar/doc.txt
Normal file
5
sys/progress_bar/doc.txt
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @defgroup sys_progress_bar A terminal progress bar
|
||||
* @ingroup sys
|
||||
* @brief Manage a progress bar on the standard output
|
||||
*/
|
106
sys/progress_bar/progress_bar.c
Normal file
106
sys/progress_bar/progress_bar.c
Normal file
@ -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
tests/progress_bar/Makefile
Normal file
26
tests/progress_bar/Makefile
Normal file
@ -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
tests/progress_bar/main.c
Normal file
73
tests/progress_bar/main.c
Normal file
@ -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
tests/progress_bar/tests/01-run.py
Executable file
38
tests/progress_bar/tests/01-run.py
Executable file
@ -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))
|
Loading…
Reference in New Issue
Block a user