Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminate encoding check in ncnmetric() #2782

Merged
merged 3 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions doc/man/man3/notcurses_metric.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ notcurses_metric - fixed-width numeric output with metric suffixes

# DESCRIPTION

**ncmetric** (and the helper wrappers **qprefix** and **bprefix**) accept
very large (or very small) non-negative numbers, and prepare formatted output
**ncnmetric** (and the helper wrappers **nc[qib]prefix**) accept
very large (or very small) non-negative integers, and prepare formatted output
of a maximum width using metric suffixes. The suffix can represent arbitrary
amounts of growth, but is designed for 1000 (**NCPREFIX**) or 1024
(**NCIPREFIX**). 1024 is used for "digital units of information", i.e. kibibytes
and gibibits. **ncmetric** supports the large suffixes KMGTPEZY (Kilo, Mega,
and gibibits. **ncnmetric** supports the large suffixes KMGTPEZY (Kilo, Mega,
Giga, Tera, Peta, Exa, Zetta, and Yotta) and the small suffixes mµnpfazy
(Milli, Micro, Nano, Pico, Femto, Atto, Zepto, and Yocto). This covers the
range 1e24 (one septillion) through 1e-24, sufficing for all possible values of
Expand All @@ -62,19 +62,19 @@ Three helper functions are provided to simplify these common cases:
// Mega, kilo, gigafoo. Use NCPREFIXSTRLEN + 1 and NCPREFIXCOLUMNS.
static inline const char*
ncqprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1000, '\0');
return ncnmetric(val, decimal, buf, omitdec, 1000, '\0');
}

// Mibi, kebi, gibibytes sans 'i' suffix. Use NCIPREFIXSTRLEN + 1.
static inline const char*
nciprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1024, '\0');
return ncnmetric(val, decimal, buf, omitdec, 1024, '\0');
}

// Mibi, kebi, gibibytes. Use NCBPREFIXSTRLEN + 1 and NCBPREFIXCOLUMNS.
static inline const char*
ncbprefix(uintmax_t val, uintmax_t decimal, char* buf, int omitdec){
return ncmetric(val, decimal, buf, omitdec, 1024, 'i');
return ncnmetric(val, decimal, buf, omitdec, 1024, 'i');
}
```

Expand All @@ -101,7 +101,7 @@ suffix, and u is the ***uprefix***. The minimum-width output will take the form
single-column value such as 5 is passed for ***val***.

Three more defines are provided to simplify formatted fixed-width output using
the results of **ncmetric**. Each of these macros accepts a character buffer
the results of **ncnmetric**. Each of these macros accepts a character buffer
holding the result of the call, and expand to *two* arguments:

* **NCPREFIXFMT(x)**
Expand All @@ -116,7 +116,8 @@ to ensure that the output is always **NCPREFIXCOLUMNS** wide.

# RETURN VALUES

**NULL** if input parameters were invalid. Otherwise, a pointer to ***buf***,
**NULL** if input parameters were invalid, or if **notcurses_init**
has not been successfully called. Otherwise, a pointer to ***buf***,
filled in with the formatted output.

# EXAMPLES
Expand All @@ -139,12 +140,19 @@ filled in with the formatted output.

# BUGS

This function is difficult to understand.
This function is difficult to understand, and takes too many arguments.

If UTF-8 is available, 'µ' (U+00B5 MICRO SIGN) will be used in place of
'u' (U+0075 LATIN SMALL LETTER U) for the 'micro-' prefix. This is
determined by **notcurses_init**/**ncdirect_init**. Once UTF-8 is detected,
it is used for the lifetime of the process. The alternative would require
accepting a **const struct notcurses** (and associated API break).

# SEE ALSO

**localeconv(3)**,
**notcurses(3)**,
**notcurses_output(3)**,
**setlocale(3)**,
**snprintf(3)**
**snprintf(3)**,
**utf-8(7)**
3 changes: 1 addition & 2 deletions include/notcurses/notcurses.h
Original file line number Diff line number Diff line change
Expand Up @@ -3790,8 +3790,7 @@ API struct ncplane* nctablet_plane(struct nctablet* t);
//
// You are encouraged to consult notcurses_metric(3).
API const char* ncnmetric(uintmax_t val, size_t s, uintmax_t decimal,
char* buf, int omitdec, uintmax_t mult,
int uprefix)
char* buf, int omitdec, uintmax_t mult, int uprefix)
__attribute__ ((nonnull (4)));

// The number of columns is one fewer, as the STRLEN expressions must leave
Expand Down
3 changes: 2 additions & 1 deletion src/lib/direct.c
Original file line number Diff line number Diff line change
Expand Up @@ -893,8 +893,9 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
}
const char* encoding = nl_langinfo(CODESET);
bool utf8 = false;
if(encoding && strcmp(encoding, "UTF-8") == 0){
if(encoding && encoding_is_utf8(encoding)){
utf8 = true;
ncmetric_use_utf8();
}
if(setup_signals(ret, (flags & NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS),
true, ncdirect_stop_minimal)){
Expand Down
27 changes: 27 additions & 0 deletions src/lib/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,33 @@ int putenv_term(const char* termname) __attribute__ ((nonnull (1)));
int set_loglevel_from_env(ncloglevel_e* loglevel)
__attribute__ ((nonnull (1)));

// glibc's _nl_normalize_charset() converts to lowercase, removing everything
// but alnums. furthermore, "cs" is a valid prefix meaning "character set".
static inline bool
encoding_is_utf8(const char *enc){
if(tolower(enc[0]) == 'c' && tolower(enc[1]) == 's'){ // strncasecmp() isn't ansi/iso
enc += 2; // skip initial "cs" if present.
}
const char utfstr[] = "utf8";
const char* match = utfstr;
while(*enc){
if(isalnum(*enc)){ // we only care about alnums
if(tolower(*enc) != tolower(*match)){
return false;
}
++match;
}
++enc;
}
if(*match){
return false;
}
return true;
}

// tell ncmetric that utf8 is available. should be per-context, but isn't.
void ncmetric_use_utf8(void);

#undef API
#undef ALLOC

Expand Down
25 changes: 13 additions & 12 deletions src/lib/metric.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,27 @@

static const wchar_t UTF8_SUBPREFIX[] = L"mµnpfazy"; // 10^24-1
static const wchar_t ASCII_SUBPREFIX[] = L"munpfazy"; // 10^24-1

// we want to use UTF8_SUBPREFIX if we have utf8 available to us. we could
// pull this out of const struct notcurses*, except these ncnmetric() doesn't
// take one, and we don't want to break the API. instead, we call this from
// notcurses_init() when we create a utf8 context. a gross hack =\.
static pthread_once_t utf8_verdict = PTHREAD_ONCE_INIT;
static const wchar_t* SUBPREFIXES = ASCII_SUBPREFIX;
static pthread_once_t utf8_detector = PTHREAD_ONCE_INIT;

// sure hope we've called setlocale() by the time we hit this!
static void
detect_utf8(void){
const char* encoding = nl_langinfo(CODESET);
if(encoding){
if(strcmp(encoding, "UTF-8") == 0){
SUBPREFIXES = UTF8_SUBPREFIX;
}
}
ncmetric_use_utf8_internal(void){
SUBPREFIXES = UTF8_SUBPREFIX;
}

void ncmetric_use_utf8(void){
pthread_once(&utf8_verdict, ncmetric_use_utf8_internal);
}

const char* ncnmetric(uintmax_t val, size_t s, uintmax_t decimal,
char* buf, int omitdec, uintmax_t mult,
int uprefix){
// FIXME this is global to the process...ick :/
fesetround(FE_TONEAREST);
pthread_once(&utf8_detector, detect_utf8);
fesetround(FE_TONEAREST); // FIXME global to the process...ick :/
// these two must have the same number of elements
const wchar_t* subprefixes = SUBPREFIXES;
const wchar_t prefixes[] = L"KMGTPEZY"; // 10^21-1 encompasses 2^64-1
Expand Down
27 changes: 3 additions & 24 deletions src/lib/notcurses.c
Original file line number Diff line number Diff line change
Expand Up @@ -1095,30 +1095,6 @@ int ncplane_destroy_family(ncplane *ncp){
return ret;
}

// glibc's _nl_normalize_charset() converts to lowercase, removing everything
// but alnums. furthermore, "cs" is a valid prefix meaning "character set".
static bool
encoding_is_utf8(const char *enc){
if(tolower(enc[0]) == 'c' && tolower(enc[1]) == 's'){ // strncasecmp() isn't ansi/iso
enc += 2; // skip initial "cs" if present.
}
const char utfstr[] = "utf8";
const char* match = utfstr;
while(*enc){
if(isalnum(*enc)){ // we only care about alnums
if(tolower(*enc) != tolower(*match)){
return false;
}
++match;
}
++enc;
}
if(*match){
return false;
}
return true;
}

// it's critical that we're using UTF-8 encoding if at all possible. since the
// client might not have called setlocale(2) (if they weren't reading the
// directions...), go ahead and try calling setlocale(LC_ALL, "") and then
Expand Down Expand Up @@ -1271,6 +1247,9 @@ notcurses_early_init(const struct notcurses_options* opts, FILE* fp, unsigned* u
free(ret);
return NULL;
}
if(utf8){
ncmetric_use_utf8();
}
return ret;
}

Expand Down
8 changes: 4 additions & 4 deletions src/lib/plot.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ typedef struct nc##X##plot { \
ncplot plot; \
} nc##X##plot; \
\
int redraw_pixelplot_##T(nc##X##plot* ncp){ \
static int redraw_pixelplot_##T(nc##X##plot* ncp){ \
if(calculate_gradient_vector(&ncp->plot, 1)){ \
return -1; \
} \
Expand Down Expand Up @@ -256,7 +256,7 @@ int redraw_pixelplot_##T(nc##X##plot* ncp){ \
return 0; \
} \
\
int redraw_plot_##T(nc##X##plot* ncp){ \
static int redraw_plot_##T(nc##X##plot* ncp){ \
if(ncp->plot.bset->geom == NCBLIT_PIXEL){ \
return redraw_pixelplot_##T(ncp); \
} \
Expand Down Expand Up @@ -429,8 +429,8 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
} \
\
static const struct blitset* \
create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T miny, const T maxy, \
const T trueminy, const T truemaxy){ \
create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, \
const T miny, const T maxy, const T trueminy, const T truemaxy){ \
/* set up ->plot.ncp first so it gets destroyed on error */ \
ncpp->plot.ncp = n; \
if(ncplane_set_widget(ncpp->plot.ncp, ncpp, (void(*)(void*))nc##X##plot_destroy)){ \
Expand Down
Loading