From 60563f3e3cecb996c5a822f816578f36ebf6a987 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Fri, 13 Dec 2024 20:02:36 +0000 Subject: [PATCH] New bar formats - default, block, granular, shaded - with the granular format providing a smoother bar using Unicode block characters (#15) --- docs/NEWS.md | 1 + po/cs.po | 16 +-- po/de.po | 16 +-- po/es.po | 16 +-- po/fr.po | 16 +-- po/pl.po | 16 +-- po/pt.po | 16 +-- po/ru.po | 16 +-- po/tr.po | 16 +-- src/include/pv-internal.h | 61 ++++++++++++ src/pv/display.c | 7 +- src/pv/format/barstyle.c | 188 ++++++++++++++++++++++++++++++++++++ src/pv/format/progressbar.c | 181 ++++++++++++++++++++++------------ 13 files changed, 439 insertions(+), 127 deletions(-) create mode 100644 src/pv/format/barstyle.c diff --git a/docs/NEWS.md b/docs/NEWS.md index 1f24f71..78ecb46 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -1,6 +1,7 @@ ### UNRELEASED * *feature:* allow decimal values such as "1.5G" with "**--size**", "**--rate-limit**", "**--buffer-size**", and "**error-skip-block**" ([#35](https://codeberg.org/a-j-wood/pv/issues/35)) + * *feature:* new bar formats "**%{bar-default}**", "**%{bar-block}**", "**%{bar-granular}**", "**%{bar-shaded}**" * *docs:* simplified the synopsis section of the manual ### 1.9.15 - 8 December 2024 diff --git a/po/cs.po b/po/cs.po index c21e96a..a39e568 100644 --- a/po/cs.po +++ b/po/cs.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "PO-Revision-Date: 2024-10-13 20:43+0000\n" "Last-Translator: mmatous \n" "Language-Team: Czech \n" @@ -411,7 +411,7 @@ msgstr "yzafpnum kMGTPEZY" msgid "yzafpnum KMGTPEZY" msgstr "yzafpnum KMGTPEZY" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "selhala alokace vyrovnávací paměti" @@ -443,17 +443,17 @@ msgstr "(žádný)" msgid "(stdin)" msgstr "(stdin)" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "b/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "B/s" @@ -462,15 +462,15 @@ msgstr "B/s" msgid "b" msgstr "b" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "B" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "ETA" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "FIN" diff --git a/po/de.po b/po/de.po index 03300c3..13b58a2 100644 --- a/po/de.po +++ b/po/de.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "PO-Revision-Date: 2024-10-13 20:43+0000\n" "Last-Translator: fnetX \n" "Language-Team: German \n" @@ -434,7 +434,7 @@ msgstr "yzafpnµm kMGTPEZY" msgid "yzafpnum KMGTPEZY" msgstr "yzafpnµm KMGTPEZY" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "Speicherzuweisung für den Puffer fehlgeschlagen" @@ -466,17 +466,17 @@ msgstr "(keine)" msgid "(stdin)" msgstr "(stdin)" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "b/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "B/s" @@ -485,15 +485,15 @@ msgstr "B/s" msgid "b" msgstr "b" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "B" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "ETA" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "Endzeit" diff --git a/po/es.po b/po/es.po index c94584f..1a695d6 100644 --- a/po/es.po +++ b/po/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -403,7 +403,7 @@ msgstr "" msgid "yzafpnum KMGTPEZY" msgstr "" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "" @@ -435,17 +435,17 @@ msgstr "" msgid "(stdin)" msgstr "" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "" @@ -454,15 +454,15 @@ msgstr "" msgid "b" msgstr "" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "" diff --git a/po/fr.po b/po/fr.po index 36e0340..e9f71dc 100644 --- a/po/fr.po +++ b/po/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "PO-Revision-Date: 2024-10-12 11:13+0000\n" "Last-Translator: a-j-wood \n" "Language-Team: French \n" @@ -438,7 +438,7 @@ msgstr "" msgid "yzafpnum KMGTPEZY" msgstr "" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "échec de l'allocation de mémoire tampon" @@ -470,17 +470,17 @@ msgstr "(aucun)" msgid "(stdin)" msgstr "" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "O/s" @@ -489,15 +489,15 @@ msgstr "O/s" msgid "b" msgstr "" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "O" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "ETA" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "" diff --git a/po/pl.po b/po/pl.po index c49eaa8..ccee774 100644 --- a/po/pl.po +++ b/po/pl.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "PO-Revision-Date: 2024-10-20 14:07+0000\n" "Last-Translator: coralpink \n" "Language-Team: Polish \n" @@ -429,7 +429,7 @@ msgstr "yzafpnum kMGTPEZY" msgid "yzafpnum KMGTPEZY" msgstr "yzafpnum KMGTPEZY" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "nie udało się zaalokować bufora" @@ -462,17 +462,17 @@ msgstr "(brak)" msgid "(stdin)" msgstr "(stdin)" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "b/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "B/s" @@ -481,15 +481,15 @@ msgstr "B/s" msgid "b" msgstr "b" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "B" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "ETA" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "FIN" diff --git a/po/pt.po b/po/pt.po index 2ad2139..eebf670 100644 --- a/po/pt.po +++ b/po/pt.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -417,7 +417,7 @@ msgstr "" msgid "yzafpnum KMGTPEZY" msgstr "" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "erro alocando o buffer" @@ -449,17 +449,17 @@ msgstr "" msgid "(stdin)" msgstr "" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "B/s" @@ -468,15 +468,15 @@ msgstr "B/s" msgid "b" msgstr "" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "ETA" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "" diff --git a/po/ru.po b/po/ru.po index a7220af..36d56ef 100644 --- a/po/ru.po +++ b/po/ru.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "PO-Revision-Date: 2024-10-22 18:07+0000\n" "Last-Translator: 0ko <0ko@users.noreply.translate.codeberg.org>\n" "Language-Team: Russian \n" @@ -412,7 +412,7 @@ msgstr "" msgid "yzafpnum KMGTPEZY" msgstr "" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "не удалось выделить память для буфера" @@ -444,17 +444,17 @@ msgstr "(нет)" msgid "(stdin)" msgstr "(стд. ввод)" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "бит/с" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "/с" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "байт/с" @@ -463,15 +463,15 @@ msgstr "байт/с" msgid "b" msgstr "бит" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "байт" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "ОЖИД" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "ЗАВЕРШ" diff --git a/po/tr.po b/po/tr.po index d5d2dc8..d38d9b6 100644 --- a/po/tr.po +++ b/po/tr.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: pv 1.8.14\n" "Report-Msgid-Bugs-To: pv@ivarch.com\n" -"POT-Creation-Date: 2024-12-12 22:09+0000\n" +"POT-Creation-Date: 2024-12-13 19:54+0000\n" "PO-Revision-Date: 2024-10-12 23:51+0000\n" "Last-Translator: a-j-wood \n" "Language-Team: Turkish \n" @@ -413,7 +413,7 @@ msgstr "yzafpnum kMGTPEZY" msgid "yzafpnum KMGTPEZY" msgstr "yzafpnum KMGTPEZY" -#: src/pv/display.c:914 src/pv/transfer.c:1056 +#: src/pv/display.c:919 src/pv/transfer.c:1056 msgid "buffer allocation failed" msgstr "arabellek tahsisi başarısız oldu" @@ -445,17 +445,17 @@ msgstr "(yok)" msgid "(stdin)" msgstr "(stdin)" -#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:59 +#: src/pv/format/averagerate.c:30 src/pv/format/progressbar.c:87 #: src/pv/format/rate.c:32 src/pv/loop.c:549 msgid "b/s" msgstr "b/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 msgid "/s" msgstr "/s" -#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:63 +#: src/pv/format/averagerate.c:34 src/pv/format/progressbar.c:91 #: src/pv/format/rate.c:36 src/pv/loop.c:549 msgid "B/s" msgstr "B/s" @@ -464,15 +464,15 @@ msgstr "B/s" msgid "b" msgstr "b" -#: src/pv/format/bytes.c:34 src/pv/transfer.c:594 +#: src/pv/format/bytes.c:35 src/pv/transfer.c:594 msgid "B" msgstr "B" -#: src/pv/format/eta.c:52 src/pv/format/eta.c:56 +#: src/pv/format/eta.c:53 src/pv/format/eta.c:57 msgid "ETA" msgstr "ETA" -#: src/pv/format/fineta.c:80 +#: src/pv/format/fineta.c:81 msgid "FIN" msgstr "FIN" diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 64bcee0..5b94c75 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -9,6 +9,9 @@ #ifndef _PV_INTERNAL_H #define _PV_INTERNAL_H 1 +#ifdef HAVE_STDINT_H +#include +#endif #include #include #include @@ -45,6 +48,10 @@ extern "C" { #define PV_SIZEOF_FILE_FDPATH 4096 #define PV_SIZEOF_DISPLAY_NAME 512 +#define PV_BARSTYLE_MAX 4 /* number of different styles allowed in a format */ +#define PV_BARSTYLE_SIZEOF_STRING 10 /* max length of a bar constituent component in bytes */ +#define PV_BARSTYLE_MAX_FILLERS 10 /* max number of bar filler strings */ + #define PV_DISPLAY_WINDOWTITLE 1 #define PV_DISPLAY_PROCESSTITLE 2 @@ -67,6 +74,50 @@ typedef enum { } pvtransfercount_t; +/* + * Structure describing a short string used as part of a progress bar, whose + * width in display characters may not be the same as its length in bytes. + */ +struct pvbarstring_spec_s { + char string[PV_BARSTYLE_SIZEOF_STRING]; + uint8_t width; + uint8_t bytes; +}; + +/* + * Structure describing a style of progress bar. + * + * The part that moves back and forth when the size isn't known, such as + * "<=>", is "indicator". + * + * The string used to populate the rest of the bar is one of "filler", which + * contains "filler_entries" array items. The first item "filler[0]" is + * used for an empty part such as " ". The last item + * "filler[filler_entries-1]" is used for a 100% full part, such as "=". + * + * If there are more than the minimum of 2 entries, then the intervening + * entries are used for partial fills. + * + * When there are only 2 entries and the bar isn't completely empty or full, + * the last filled portion of the bar is filled with "tip" instead of the + * last filler item to indicate the tip of the bar, such as ">". + * + * Note that only the indicator is expected to ever be wider than 1 display + * character. All other items are expected to have a width of 0 or 1. + * + * The "style_id" is an opaque identifier that should only be used to + * determine whether two styles are the same. It is always nonzero. + */ +struct pvbarstyle_s { + uint8_t style_id; + uint8_t filler_entries; + struct pvbarstring_spec_s indicator; + struct pvbarstring_spec_s tip; + struct pvbarstring_spec_s filler[PV_BARSTYLE_MAX_FILLERS]; +}; +typedef struct pvbarstyle_s *pvbarstyle_t; + + /* * Structure for holding PV internal state. Opaque outside the PV library. * @@ -183,12 +234,15 @@ struct pvstate_s { struct pvdisplay_segment_s { /* format string broken into segments */ /* See pv__format_init() for more details. */ int type; /* component type, -1 for static string */ + int parameter; /* component parameter, such as bar style index */ size_t chosen_size; /* "n" from %A, or 0 */ size_t offset; /* start offset of this segment */ size_t bytes; /* length of segment in bytes */ size_t width; /* displayed width of segment */ } format[PV_FORMAT_ARRAY_MAX]; + struct pvbarstyle_s barstyle[PV_BARSTYLE_MAX]; + /* The last-written "n" bytes. */ char lastwritten_buffer[PV_SIZEOF_LASTWRITTEN_BUFFER]; @@ -412,6 +466,9 @@ long pv_seconds_remaining(const off_t, const off_t, const long double); void pv_si_prefix(long double *, char *, const long double, pvtransfercount_t); void pv_describe_amount(char *, size_t, char *, long double, char *, char *, pvtransfercount_t); +bool pv_barstyle(pvbarstyle_t, const char *); +int pv_display_barstyle_index(pvdisplay_t, const char *); + size_t pv_formatter_segmentcontent(char *, pvformatter_args_t); /* @@ -438,6 +495,10 @@ size_t pv_formatter_segmentcontent(char *, pvformatter_args_t); size_t pv_formatter_progress(pvformatter_args_t); size_t pv_formatter_progress_bar_only(pvformatter_args_t); size_t pv_formatter_progress_amount_only(pvformatter_args_t); +size_t pv_formatter_bar_default(pvformatter_args_t); +size_t pv_formatter_bar_block(pvformatter_args_t); +size_t pv_formatter_bar_granular(pvformatter_args_t); +size_t pv_formatter_bar_shaded(pvformatter_args_t); size_t pv_formatter_timer(pvformatter_args_t); size_t pv_formatter_eta(pvformatter_args_t); size_t pv_formatter_fineta(pvformatter_args_t); diff --git a/src/pv/display.c b/src/pv/display.c index 294b6ef..e0ec9cf 100644 --- a/src/pv/display.c +++ b/src/pv/display.c @@ -539,8 +539,13 @@ static bool pv__format_numeric(pvstate_t state, pvdisplay_t display) /*@keep@ */ static struct pvdisplay_component_s format_component_array[] = { { "p", &pv_formatter_progress, true }, { "{progress}", &pv_formatter_progress, true }, - { "{progress-bar-only}", &pv_formatter_progress_bar_only, true }, { "{progress-amount-only}", &pv_formatter_progress_amount_only, false }, + { "{progress-bar-only}", &pv_formatter_bar_default, true }, + { "{bar}", &pv_formatter_bar_default, true }, + { "{bar-default}", &pv_formatter_bar_default, true }, + { "{bar-block}", &pv_formatter_bar_block, true }, + { "{bar-granular}", &pv_formatter_bar_granular, true }, + { "{bar-shaded}", &pv_formatter_bar_shaded, true }, { "t", &pv_formatter_timer, false }, { "{timer}", &pv_formatter_timer, false }, { "e", &pv_formatter_eta, false }, diff --git a/src/pv/format/barstyle.c b/src/pv/format/barstyle.c new file mode 100644 index 0000000..5613954 --- /dev/null +++ b/src/pv/format/barstyle.c @@ -0,0 +1,188 @@ +/* + * Formatter functions for styled progress bars. + * + * Copyright 2024 Andrew Wood + * + * License GPLv3+: GNU GPL version 3 or later; see `docs/COPYING'. + */ + +#include "config.h" +#include "pv.h" +#include "pv-internal.h" + +#include +#if defined(ENABLE_NLS) && defined(HAVE_WCHAR_H) +#include +#if defined(HAVE_WCTYPE_H) +#include +#endif +#endif + + +/* + * Populate "style" with the named bar style, falling back to the default if + * the name was not recognised. Returns true if the named style was found. + * + * Note that strings are copied into the structure, rather than just + * updating pointers, to maintain separation of concern between different + * parts of the code - and because on 64-bit architectures, the sizes of the + * data types and their associated alignment padding tend to outweigh the + * memory savings from using pointers and shared strings. + */ +bool pv_barstyle(pvbarstyle_t style, const char *name) +{ +#define populate_string(item, str, w) { \ + item.width = w; \ + item.bytes = strlen(str); /* flawfinder: ignore */ \ + if (item.bytes > 0 && item.bytes <= PV_BARSTYLE_SIZEOF_STRING) \ + memcpy(item.string, str, item.bytes); /* flawfinder: ignore */ \ +} + /* + * flawfinder - strlen() on null-terminated static strings is OK, + * and with the memcpy(), we check the buffer is big enough. + */ + + memset(style, 0, sizeof(*style)); + + if (0 == strcmp(name, "block")) { + + style->style_id = 2; + + populate_string(style->indicator, "◀▶", 2); + populate_string(style->tip, "", 0); + + populate_string(style->filler[0], " ", 1); + populate_string(style->filler[1], "█", 1); + + style->filler_entries = 2; + + return true; + + } else if (0 == strcmp(name, "granular")) { + + style->style_id = 3; + + populate_string(style->indicator, "◀▶", 2); + populate_string(style->tip, "", 0); + + populate_string(style->filler[0], " ", 1); + populate_string(style->filler[1], "▏", 1); + populate_string(style->filler[2], "▎", 1); + populate_string(style->filler[3], "▍", 1); + populate_string(style->filler[4], "▌", 1); + populate_string(style->filler[5], "▋", 1); + populate_string(style->filler[6], "▊", 1); + populate_string(style->filler[7], "▉", 1); + populate_string(style->filler[8], "█", 1); + + style->filler_entries = 9; + + return true; + + } else if (0 == strcmp(name, "shaded")) { + + style->style_id = 4; + + populate_string(style->indicator, "▒▓▒", 3); + populate_string(style->tip, "", 0); + + populate_string(style->filler[0], "░", 1); + populate_string(style->filler[1], "▒", 1); + populate_string(style->filler[2], "▓", 1); + populate_string(style->filler[3], "█", 1); + + style->filler_entries = 4; + + return true; + } + + /* Default style. */ + + style->style_id = 1; + + populate_string(style->indicator, "<=>", 3); + populate_string(style->tip, ">", 1); + + populate_string(style->filler[0], " ", 1); + populate_string(style->filler[1], "=", 1); + + style->filler_entries = 2; + + if (0 == strcmp(name, "default")) + return true; + + return false; +} + + +/* + * Return the index into display->barstyle for the style with the given + * name, adding that style to the array if it's not there already and + * there's room. + * + * If there is no room, returns zero, so the first style is re-used. + */ +int pv_display_barstyle_index(pvdisplay_t display, const char *name) +{ + struct pvbarstyle_s style; + int barstyle_index; +#ifdef ENABLE_DEBUGGING + bool found; +#endif + + memset(&style, 0, sizeof(style)); +#ifdef ENABLE_DEBUGGING + found = pv_barstyle(&style, name); + if (!found) + debug("%s: %s", name, "bar style not found, using default"); +#else + (void) pv_barstyle(&style, name); +#endif + + for (barstyle_index = 0; barstyle_index < PV_BARSTYLE_MAX && display->barstyle[barstyle_index].style_id > 0; + barstyle_index++) { + if (display->barstyle[barstyle_index].style_id == style.style_id) { + debug("%s: %s: %d", name, "found in bar style array", barstyle_index); + return barstyle_index; + } + } + + if (barstyle_index >= PV_BARSTYLE_MAX) { + debug("%s: %s", name, "no room to add another bar style - returning 0"); + return 0; + } + + memcpy(&(display->barstyle[barstyle_index]), &style, sizeof(style)); /* flawfinder: ignore */ + /* flawfinder - the destination is an array element of the right size. */ + debug("%s: %s: %d", name, "added to bar style array", barstyle_index); + return barstyle_index; +} + + +size_t pv_formatter_bar_default(pvformatter_args_t args) +{ + if (0 == args->segment->parameter) + args->segment->parameter = 1 + pv_display_barstyle_index(args->display, "default"); + return pv_formatter_progress_bar_only(args); +} + +size_t pv_formatter_bar_block(pvformatter_args_t args) +{ + if (0 == args->segment->parameter) + args->segment->parameter = 1 + pv_display_barstyle_index(args->display, "block"); + return pv_formatter_progress_bar_only(args); +} + +size_t pv_formatter_bar_granular(pvformatter_args_t args) +{ + if (0 == args->segment->parameter) + args->segment->parameter = 1 + pv_display_barstyle_index(args->display, "granular"); + return pv_formatter_progress_bar_only(args); +} + +size_t pv_formatter_bar_shaded(pvformatter_args_t args) +{ + if (0 == args->segment->parameter) + args->segment->parameter = 1 + pv_display_barstyle_index(args->display, "shaded"); + return pv_formatter_progress_bar_only(args); +} diff --git a/src/pv/format/progressbar.c b/src/pv/format/progressbar.c index 6dc73e4..51735a6 100644 --- a/src/pv/format/progressbar.c +++ b/src/pv/format/progressbar.c @@ -12,6 +12,18 @@ #include +/* Convenience macro for appending a string to the buffer. */ +#define append_to_buffer(x) { \ + if (buffer_offset < (buffer_size - x.bytes)) { \ + memcpy(&(buffer[buffer_offset]), x.string, x.bytes); /* flawfinder: ignore */ \ + buffer_offset += x.bytes; \ + } \ +} +/* + * flawfinder - we are checking that there is room in the destination + * buffer, given its size and our current offset. + */ + /* * Write a progress bar to a buffer, in known-size or rate-gauge mode - a @@ -29,38 +41,55 @@ * * This is only called by pv_formatter_progress(). */ -static size_t pv_formatter_progress_knownsize(pvstate_t state, pvdisplay_t display, char *buffer, size_t buffer_size, - size_t width, bool bar_sides, bool include_bar, bool include_amount) +static size_t pv_formatter_progress_knownsize(pvformatter_args_t args, char *buffer, size_t buffer_size, bool bar_sides, + bool include_bar, bool include_amount) { char after_bar[32]; /* flawfinder: ignore - only populated by pv_snprintf(). */ size_t after_bar_bytes, after_bar_width; size_t bar_area_width, filled_bar_width, buffer_offset, pad_count; - int bar_percentage; + double bar_percentage; + pvbarstyle_t style; + size_t full_cell_index; + bool has_tip = false; buffer[0] = '\0'; + if (args->segment->parameter > 0 && args->segment->parameter <= PV_BARSTYLE_MAX) { + style = &(args->display->barstyle[args->segment->parameter - 1]); + } else { + style = &(args->display->barstyle[0]); + } + + full_cell_index = style->filler_entries; + if (full_cell_index > 0) + full_cell_index--; + if (1 == full_cell_index && style->tip.width > 0) + has_tip = true; + memset(after_bar, 0, sizeof(after_bar)); - if (state->control.size > 0) { + if (args->state->control.size > 0) { /* Percentage of data transferred. */ - bar_percentage = state->calc.percentage; - (void) pv_snprintf(after_bar, sizeof(after_bar), " %3ld%%", bar_percentage); + bar_percentage = (double) (args->state->calc.percentage); + (void) pv_snprintf(after_bar, sizeof(after_bar), " %3ld%%", (int) bar_percentage); } else { /* Current rate vs max rate. */ - bar_percentage = 0; - if (state->calc.rate_max > 0) { - bar_percentage = (int) (100.0 * state->calc.transfer_rate / state->calc.rate_max); + bar_percentage = 0.0; + if (args->state->calc.rate_max > 0) { + bar_percentage = + (double) (100.0 * args->state->calc.transfer_rate / args->state->calc.rate_max); } /*@-mustfreefresh@ */ - if (state->control.bits && !state->control.linemode) { + if (args->state->control.bits && !args->state->control.linemode) { /* bits per second */ pv_describe_amount(after_bar, sizeof(after_bar), "/%s", - 8.0 * state->calc.rate_max, "", _("b/s"), display->count_type); + 8.0 * args->state->calc.rate_max, "", _("b/s"), args->display->count_type); } else { /* bytes or lines per second */ pv_describe_amount(after_bar, sizeof(after_bar), - "/%s", state->calc.rate_max, _("/s"), _("B/s"), display->count_type); + "/%s", args->state->calc.rate_max, _("/s"), _("B/s"), + args->display->count_type); } /*@+mustfreefresh@ *//* splint: see above about gettext(). */ } @@ -73,6 +102,10 @@ static size_t pv_formatter_progress_knownsize(pvstate_t state, pvdisplay_t displ after_bar_width = pv_strwidth(after_bar, after_bar_bytes); if (!include_bar) { + /* + * Only returning the "after bar" portion - the amount + * (progress or max rate). + */ if (buffer_size < after_bar_bytes) return 0; if (after_bar_bytes > 1) { @@ -85,24 +118,28 @@ static size_t pv_formatter_progress_knownsize(pvstate_t state, pvdisplay_t displ } if (bar_sides) { - if (width < (after_bar_width + 2)) + if (args->segment->width < (after_bar_width + 2)) return 0; - bar_area_width = width - after_bar_width - 2; + bar_area_width = args->segment->width - after_bar_width - 2; } else { - if (width < after_bar_width) + if (args->segment->width < after_bar_width) return 0; - bar_area_width = width - after_bar_width; + bar_area_width = args->segment->width - after_bar_width; } - if (bar_area_width > buffer_size - 16) - bar_area_width = buffer_size - 16; + /* + * Make sure there is enough room in the buffer, even if all of the + * bar characters are very wide. + */ + if (4 * bar_area_width > buffer_size - 16) + bar_area_width = (buffer_size - 16) / 4; - filled_bar_width = (size_t) (bar_area_width * bar_percentage) / 100; + filled_bar_width = (size_t) ((bar_area_width * bar_percentage) / 100); /* Leave room for the tip of the bar. */ - if (filled_bar_width > 0) + if (has_tip && filled_bar_width > 0) filled_bar_width--; - debug("width=%d bar_area_width=%d filled_bar_width=%d", width, bar_area_width, filled_bar_width); + debug("width=%d bar_area_width=%d filled_bar_width=%d", args->segment->width, bar_area_width, filled_bar_width); buffer_offset = 0; @@ -112,22 +149,35 @@ static size_t pv_formatter_progress_knownsize(pvstate_t state, pvdisplay_t displ } /* The bar portion. */ - for (pad_count = 0; pad_count < filled_bar_width && buffer_offset < buffer_size - 1; pad_count++) { - if (pad_count < bar_area_width) - buffer[buffer_offset++] = '='; + for (pad_count = 0; pad_count < filled_bar_width && buffer_offset < buffer_size - 4; pad_count++) { + if (pad_count < bar_area_width) { + append_to_buffer(style->filler[full_cell_index]); + } } /* The tip of the bar, if not at 100%. */ - if (pad_count < bar_area_width) { - if (buffer_offset < buffer_size - 1) - buffer[buffer_offset++] = '>'; + if (has_tip && pad_count < bar_area_width) { + append_to_buffer(style->tip); + pad_count++; + } + + /* A partial cell, if there are intermediates and we're not at 100%. */ + if (pad_count < bar_area_width && full_cell_index > 1 && !has_tip) { + double exact_width = (((double) bar_area_width) * bar_percentage) / 100.0; + double cell_portion = exact_width - (double) filled_bar_width; + double cell_index_double = ((double) full_cell_index) * cell_portion; + size_t cell_index = (size_t) cell_index_double; + + if (cell_index > full_cell_index) + cell_index = full_cell_index; + + append_to_buffer(style->filler[cell_index]); pad_count++; } /* The spaces after the bar. */ for (; pad_count < bar_area_width; pad_count++) { - if (buffer_offset < buffer_size - 1) - buffer[buffer_offset++] = ' '; + append_to_buffer(style->filler[0]); } if (bar_sides) { @@ -158,27 +208,37 @@ static size_t pv_formatter_progress_knownsize(pvstate_t state, pvdisplay_t displ * * This is only called by pv_formatter_progress(). */ -static size_t pv_formatter_progress_unknownsize(pvstate_t state, /*@unused@ */ - __attribute__((unused)) pvdisplay_t display, char *buffer, - size_t buffer_size, size_t width, bool bar_sides) +static size_t pv_formatter_progress_unknownsize(pvformatter_args_t args, char *buffer, + size_t buffer_size, bool bar_sides) { size_t bar_area_width, buffer_offset, pad_count; size_t indicator_position; + pvbarstyle_t style; buffer[0] = '\0'; - if (bar_sides) { - if (width < 6) - return 0; - bar_area_width = width - 5; + if (args->segment->parameter > 0 && args->segment->parameter <= PV_BARSTYLE_MAX) { + style = &(args->display->barstyle[args->segment->parameter - 1]); } else { - if (width < 5) - return 0; - bar_area_width = width - 3; + style = &(args->display->barstyle[0]); } - if (bar_area_width > buffer_size - 16) - bar_area_width = buffer_size - 16; + if (bar_sides) { + if (args->segment->width < (style->indicator.width + 3)) + return 0; + bar_area_width = args->segment->width - (style->indicator.width + 2); + } else { + if (args->segment->width < (style->indicator.width + 2)) + return 0; + bar_area_width = args->segment->width - style->indicator.width; + } + + /* + * Make sure there is enough room in the buffer, even if all of the + * bar characters are very wide. + */ + if (4 * bar_area_width > buffer_size - 16) + bar_area_width = (buffer_size - 16) / 4; /* * Note that pv_calculate_transfer_rate() sets the percentage when @@ -186,7 +246,7 @@ static size_t pv_formatter_progress_unknownsize(pvstate_t state, /*@unused@ */ * here we make values above 100 send the indicator back down again, * so it moves back and forth. */ - indicator_position = (size_t) (state->calc.percentage); + indicator_position = (size_t) (args->state->calc.percentage); if (indicator_position > 200) indicator_position = indicator_position % 200; if (indicator_position > 100 && indicator_position <= 200) @@ -201,21 +261,19 @@ static size_t pv_formatter_progress_unknownsize(pvstate_t state, /*@unused@ */ /* The spaces before the indicator. */ for (pad_count = 0; pad_count < (bar_area_width * indicator_position) / 100; pad_count++) { - if (pad_count < bar_area_width && buffer_offset < buffer_size - 1) - buffer[buffer_offset++] = ' '; + if (pad_count < bar_area_width) { + append_to_buffer(style->filler[0]); + } } /* The indicator. */ - if (buffer_offset < buffer_size - 4) { - buffer[buffer_offset++] = '<'; - buffer[buffer_offset++] = '='; - buffer[buffer_offset++] = '>'; + if (buffer_offset < buffer_size - style->indicator.bytes) { + append_to_buffer(style->indicator); } /* The spaces after the indicator. */ for (; pad_count < bar_area_width; pad_count++) { - if (pad_count < bar_area_width && buffer_offset < buffer_size - 1) - buffer[buffer_offset++] = ' '; + append_to_buffer(style->filler[0]); } if (bar_sides) { @@ -240,17 +298,18 @@ size_t pv_formatter_progress(pvformatter_args_t args) content[0] = '\0'; + if (0 == args->segment->parameter) + args->segment->parameter = 1 + pv_display_barstyle_index(args->display, "default"); + if (0 == args->buffer_size) return 0; if (args->state->control.size > 0 || args->state->control.rate_gauge) { /* Known size or rate gauge - bar with percentage. */ - bytes = - pv_formatter_progress_knownsize(args->state, args->display, content, sizeof(content), args->segment->width, true, true, - true); + bytes = pv_formatter_progress_knownsize(args, content, sizeof(content), true, true, true); } else { /* Unknown size - back-and-forth moving indicator. */ - bytes = pv_formatter_progress_unknownsize(args->state, args->display, content, sizeof(content), args->segment->width, true); + bytes = pv_formatter_progress_unknownsize(args, content, sizeof(content), true); } content[bytes] = '\0'; @@ -269,18 +328,18 @@ size_t pv_formatter_progress_bar_only(pvformatter_args_t args) content[0] = '\0'; + if (0 == args->segment->parameter) + args->segment->parameter = 1 + pv_display_barstyle_index(args->display, "default"); + if (0 == args->buffer_size) return 0; if (args->state->control.size > 0 || args->state->control.rate_gauge) { /* Known size or rate gauge - bar with percentage. */ - bytes = - pv_formatter_progress_knownsize(args->state, args->display, content, sizeof(content), args->segment->width, false, true, - false); + bytes = pv_formatter_progress_knownsize(args, content, sizeof(content), false, true, false); } else { /* Unknown size - back-and-forth moving indicator. */ - bytes = - pv_formatter_progress_unknownsize(args->state, args->display, content, sizeof(content), args->segment->width, false); + bytes = pv_formatter_progress_unknownsize(args, content, sizeof(content), false); } content[bytes] = '\0'; @@ -304,9 +363,7 @@ size_t pv_formatter_progress_amount_only(pvformatter_args_t args) if (args->state->control.size > 0 || args->state->control.rate_gauge) { /* Known size or rate gauge - percentage or rate. */ - bytes = - pv_formatter_progress_knownsize(args->state, args->display, content, sizeof(content), args->segment->width, false, - false, true); + bytes = pv_formatter_progress_knownsize(args, content, sizeof(content), false, false, true); } else { /* Unknown size - no number. */ return 0;