Added str_format(), echofmt(), is_letter(), is_digit(), is_hexdigit(), is_upper(), is_lower(), fmt_fixed(). Renamed fmti() to fmt_int(), and fmtf() to fmt_float().

This commit is contained in:
Revar Desmera 2020-03-14 04:23:33 -07:00
parent 602e850d24
commit 8879b7a4d9
2 changed files with 270 additions and 32 deletions

View file

@ -1,5 +1,14 @@
//////////////////////////////////////////////////////////////////////
// LibFile: strings.scad
// String manipulation and formatting functions.
// To use, add the following lines to the beginning of your file:
// ```
// use <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
// Section: String Operations
//-----------------------------------------------------------------------------
// Function: substr()
// Usage:
@ -26,6 +35,7 @@ function _substr(str,pos,len,substr="") =
len <= 0 || pos>=len(str) ? substr :
_substr(str, pos+1, len-1, str(substr, str[pos]));
// Function suffix()
// Usage:
// suffix(str,len)
@ -50,6 +60,7 @@ function str_join(list,sep="",_i=0, _result="") =
_i >= len(list)-1 ? (_i==len(list) ? _result : str(_result,list[_i])) :
str_join(list,sep,_i+1,str(_result,list[_i],sep));
// Function: downcase()
// Usage:
// downcase(str)
@ -57,12 +68,27 @@ function str_join(list,sep="",_i=0, _result="") =
// Returns the string with the standard ASCII upper case letters A-Z replaced
// by their lower case versions.
// Arguments:
// str = string to convert
// str = String to convert.
// Example:
// downcase("ABCdef"); // Returns "abcdef"
function downcase(str) =
str_join([for(char=str) let(code=ord(char)) code>=65 && code<=90 ? chr(code+32) : char]);
// Function: upcase()
// Usage:
// upcase(str)
// Description:
// Returns the string with the standard ASCII lower case letters a-z replaced
// by their upper case versions.
// Arguments:
// str = String to convert.
// Example:
// upcase("ABCdef"); // Returns "ABCDEF"
function upcase(str) =
str_join([for(char=str) let(code=ord(char)) code>=97 && code<=122 ? chr(code-32) : char]);
// Function: str_int()
// Usage:
// str_int(str, [base])
@ -71,8 +97,8 @@ function downcase(str) =
// conversion fails. Digits above 9 are represented using letters A-F in either
// upper case or lower case.
// Arguments:
// str = string to convert
// base = base for conversion, from 2-16. Default: 10
// str = String to convert.
// base = Base for conversion, from 2-16. Default: 10
// Example:
// str_int("349"); // Returns 349
// str_int("-37"); // Returns -37
@ -98,6 +124,7 @@ function _str_int_recurse(str,base,i) =
) i==0 ? last_digit :
_str_int_recurse(str,base,i-1)*base + last_digit;
// Function: str_float()
// Usage:
// str_float(str)
@ -105,7 +132,7 @@ function _str_int_recurse(str,base,i) =
// Converts a string to a floating point number. Returns NaN if the
// conversion fails.
// Arguments:
// str = string to convert
// str = String to convert.
// Example:
// str_float("44"); // Returns 44
// str_float("3.4"); // Returns 3.4
@ -125,6 +152,7 @@ function str_float(str) =
let( dsplit = str_split(str,["."]))
str_int(dsplit[0])+str_int(dsplit[1])/pow(10,len(dsplit[1]));
// Function: str_frac()
// Usage:
// str_frac(str,[mixed],[improper],[signed])
@ -138,7 +166,7 @@ function str_float(str) =
// the numerator is smaller than the denominator. If you set `signed` to false then the leading sign character is not permitted.
// The empty string evaluates to zero. Any invalid string evaluates to NaN.
// Arguments:
// str = string to convert
// str = String to convert.
// mixed = set to true to accept mixed fractions, false to reject them. Default: true
// improper = set to true to accept improper fractions, false to reject them. Default: true
// signed = set to true to accept a leading sign character, false to reject. Default: true
@ -177,6 +205,7 @@ function str_frac(str,mixed=true,improper=true,signed=true) =
denominator<0 ? (0/0) : numerator/denominator
);
// Function: str_num()
// Usage:
// str_num(str)
@ -207,7 +236,7 @@ function str_num(str) =
// If keep_nulls is true then the output will have length equal to `len(sep)+1`, possibly with trailing null strings
// if the string runs out before the separator list.
// Arguments
// str = string to split
// str = String to split.
// sep = a string or list of strings to use for the separator
// keep_nulls = boolean value indicating whether to keep null strings in the output list. Default: true
// Example:
@ -219,17 +248,17 @@ function str_num(str) =
// str_split("abc+def-qrs*iop",["-","+","*"]); // Returns ["abc+def", "qrs*iop", "", ""]
function str_split(str,sep,keep_nulls=true) =
!keep_nulls ? _remove_empty_strs(str_split(str,sep,keep_nulls=true)) :
is_list(sep) ? str_split_recurse(str,sep,i=0,result=[]) :
is_list(sep) ? _str_split_recurse(str,sep,i=0,result=[]) :
let( cutpts = concat([-1],sort(flatten(search(sep, str,0))),[len(str)]))
[for(i=[0:len(cutpts)-2]) substr(str,cutpts[i]+1,cutpts[i+1]-cutpts[i]-1)];
function str_split_recurse(str,sep,i,result) =
function _str_split_recurse(str,sep,i,result) =
i == len(sep) ? concat(result,[str]) :
let(
pos = search(sep[i], str),
end = pos==[] ? len(str) : pos[0]
)
str_split_recurse(
_str_split_recurse(
substr(str,end+1),
sep, i+1,
concat(result, [substr(str,0,end)])
@ -253,6 +282,7 @@ function _str_cmp(str,sindex,pattern) =
function _str_cmp_recurse(str,sindex,pattern,plen,pindex=0,) =
pindex < plen && pattern[pindex]==str[sindex] ? _str_cmp_recurse(str,sindex+1,pattern,plen,pindex+1): (pindex==plen);
// Function: str_find()
// Usage:
// str_find(str,pattern,[last],[all],[start])
@ -264,7 +294,7 @@ function _str_cmp_recurse(str,sindex,pattern,plen,pindex=0,) =
// and `last` is true then the search will find the pattern if it begins at index `start`. If no match exists, returns undef.
// If you set `all` to true then all str_find() returns all of the matches in a list, or an empty list if there are no matches.
// Arguments:
// str = string to search
// str = String to search.
// pattern = string pattern to search for
// last = set to true to return the last match. Default: false
// all = set to true to return all matches as a list. Overrides last. Default: false
@ -313,8 +343,8 @@ function _str_find_all(str,pattern) =
// Returns true if the input string `str` starts with the specified string pattern, `pattern`.
// Otherwise returns false.
// Arguments:
// str = string to search
// pattern = string pattern to search for
// str = String to search.
// pattern = String pattern to search for.
// Example:
// starts_with("abcdef","abc"); // Returns true
// starts_with("abcdef","def"); // Returns false
@ -329,8 +359,8 @@ function starts_with(str,pattern) = _str_cmp(str,0,pattern);
// Returns true if the input string `str` ends with the specified string pattern, `pattern`.
// Otherwise returns false.
// Arguments:
// str = string to search
// pattern = string pattern to search for
// str = String to search.
// pattern = String pattern to search for.
// Example:
// ends_with("abcdef","def"); // Returns true
// ends_with("abcdef","de"); // Returns false
@ -346,6 +376,7 @@ function _str_count_trailing(s,c,_i=0) =
(_i>=len(s)||!in_list(s[len(s)-1-_i],[each c]))? _i :
_str_count_trailing(s,c,_i=_i+1);
// Function: str_strip_leading()
// Usage:
// str_strip_leading(s,c);
@ -391,9 +422,9 @@ function str_strip_trailing(s,c) = substr(s,len=len(s)-_str_count_trailing(s,c))
function str_strip(s,c) = str_strip_trailing(str_strip_leading(s,c),c);
// Function: fmti()
// Function: fmt_int()
// Usage:
// fmti(i, [mindigits]);
// fmt_int(i, [mindigits]);
// Description:
// Formats an integer number into a string. This can handle larger numbers than `str()`.
// Arguments:
@ -401,10 +432,10 @@ function str_strip(s,c) = str_strip_trailing(str_strip_leading(s,c),c);
// mindigits = If the number has fewer than this many digits, pad the front with zeros until it does. Default: 1.
// Example:
// str(123456789012345); // Returns "1.23457e+14"
// fmti(123456789012345); // Returns "123456789012345"
// fmti(-123456789012345); // Returns "-123456789012345"
function fmti(i,mindigits=1) =
i<0? str("-", fmti(-i)) :
// fmt_int(123456789012345); // Returns "123456789012345"
// fmt_int(-123456789012345); // Returns "-123456789012345"
function fmt_int(i,mindigits=1) =
i<0? str("-", fmt_int(-i)) :
let(i=floor(i), e=floor(log(i)))
i==0? "0" :
str_join(
@ -415,9 +446,33 @@ function fmti(i,mindigits=1) =
);
// Function: fmtf()
// Function: fmt_fixed()
// Usage:
// fmtf(f,[sig]);
// s = fmt_fixed(f, [digits]);
// Description:
// Given a floating point number, formats it into a string with the given number of digits after the decimal point.
// Arguments:
// f = The floating point number to format.
// digits = The number of digits after the decimal to show. Default: 6
function fmt_fixed(f,digits=6) =
assert(is_int(digits))
assert(digits>0)
is_list(f)? str("[",str_join(sep=", ", [for (g=f) fmt_fixed(g,digits=digits)]),"]") :
str(f)=="nan"? "nan" :
str(f)=="inf"? "inf" :
f<0? str("-",fmt_fixed(-f,digits=digits)) :
assert(is_num(f))
let(
sc = pow(10,digits),
scaled = floor(f * sc + 0.5),
whole = floor(scaled/sc),
part = floor(scaled-(whole*sc))
) str(fmt_int(whole),".",fmt_int(part,digits));
// Function: fmt_float()
// Usage:
// fmt_float(f,[sig]);
// Description:
// Formats the given floating point number `f` into a string with `sig` significant digits.
// Strips trailing `0`s after the decimal point. Strips trailing decimal point.
@ -427,22 +482,22 @@ function fmti(i,mindigits=1) =
// f = The floating point number to format.
// sig = The number of significant digits to display. Default: 12
// Example:
// fmtf(PI,12); // Returns: "3.14159265359"
// fmtf([PI,-16.75],12); // Returns: "[3.14159265359, -16.75]"
function fmtf(f,sig=12) =
// fmt_float(PI,12); // Returns: "3.14159265359"
// fmt_float([PI,-16.75],12); // Returns: "[3.14159265359, -16.75]"
function fmt_float(f,sig=12) =
assert(is_int(sig))
assert(sig>0)
is_list(f)? str("[",str_join(sep=", ", [for (g=f) fmtf(g,sig=sig)]),"]") :
is_list(f)? str("[",str_join(sep=", ", [for (g=f) fmt_float(g,sig=sig)]),"]") :
f==0? "0" :
str(f)=="nan"? "nan" :
str(f)=="inf"? "inf" :
f<0? str("-",fmtf(-f,sig=sig)) :
f<0? str("-",fmt_float(-f,sig=sig)) :
assert(is_num(f))
let(
e = floor(log(f)),
mv = sig - e - 1
) mv == 0? fmti(floor(f + 0.5)) :
(e<-sig/2||mv<0)? str(fmtf(f*pow(10,-e),sig=sig),"e",e) :
) mv == 0? fmt_int(floor(f + 0.5)) :
(e<-sig/2||mv<0)? str(fmt_float(f*pow(10,-e),sig=sig),"e",e) :
let(
ff = f + pow(10,-mv)*0.5,
whole = floor(ff),
@ -453,11 +508,194 @@ function fmtf(f,sig=12) =
str_strip_trailing(
str_join([
".",
fmti(part, mindigits=mv)
fmt_int(part, mindigits=mv)
]),
"0."
)
]);
// Function: escape_html()
// Usage:
// echo(escape_html(s));
// Description:
// Converts "<", ">", "&", and double-quote chars to their entity encoding so that echoing the strong will show it verbatim.
function escape_html(s) =
str_join([
for (c=s)
c=="<"? "&lt;" :
c==">"? "&gt;" :
c=="&"? "&amp;" :
c=="\""? "&quot;" :
c
]);
// Function: is_lower()
// Usage:
// x = is_lower(s);
// Description:
// Returns true if all the characters in the given string are lowercase letters. (a-z)
function is_lower(s) =
assert(is_string(s))
s==""? false :
len(s)>1? all([for (v=s) is_lower(v)]) :
let(v = ord(s[0])) (v>=ord("a") && v<=ord("z"));
// Function: is_upper()
// Usage:
// x = is_upper(s);
// Description:
// Returns true if all the characters in the given string are uppercase letters. (A-Z)
function is_upper(s) =
assert(is_string(s))
s==""? false :
len(s)>1? all([for (v=s) is_upper(v)]) :
let(v = ord(s[0])) (v>=ord("A") && v<=ord("Z"));
// Function: is_digit()
// Usage:
// x = is_digit(s);
// Description:
// Returns true if all the characters in the given string are digits. (0-9)
function is_digit(s) =
assert(is_string(s))
s==""? false :
len(s)>1? all([for (v=s) is_digit(v)]) :
let(v = ord(s[0])) (v>=ord("0") && v<=ord("9"));
// Function: is_hexdigit()
// Usage:
// x = is_hexdigit(s);
// Description:
// Returns true if all the characters in the given string are valid hexadecimal digits. (0-9 or a-f or A-F))
function is_hexdigit(s) =
assert(is_string(s))
s==""? false :
len(s)>1? all([for (v=s) is_hexdigit(v)]) :
let(v = ord(s[0]))
(v>=ord("0") && v<=ord("9")) ||
(v>=ord("A") && v<=ord("F")) ||
(v>=ord("a") && v<=ord("f"));
// Function: is_letter()
// Usage:
// x = is_letter(s);
// Description:
// Returns true if all the characters in the given string are standard ASCII letters. (A-Z or a-z)
function is_letter(s) =
assert(is_string(s))
s==""? false :
all([for (v=s) is_lower(v) || is_upper(v)]);
// Function: str_format()
// Usage:
// s = str_format(fmt, vals);
// Description:
// Given a format string and a list of values, inserts the values into the placeholders in the format string and returns it.
// Formatting placeholders have the following syntax:
// - A leading `{` character to show the start of the placeholder.
// - An integer index into the `vals` list to specify which value should be formatted at that place. If not given, the first placeholder will use index `0`, the second will use index `1`, etc.
// - An optional `:` separator to indicate that what follows if a formatting specifier. If not given, no formatting info follows.
// - An optional `-` character to indicate that the value should be left justified if the value needs field width padding. If not given, right justification is used.
// - An optional `0` character to indicate that the field should be padded with `0`s. If not given, spaces will be used for padding.
// - An optional integer field width, which the value should be padded to. If not given, no padding will be performed.
// - An optional `.` followed by an integer precision length, for specifying how many digits to display in numeric formats. If not give, 6 digits is assumed.
// - An optional letter to indicate the formatting style to use. If not given, `s` is assumed, which will do it's generic best to format any data type.
// - A trailing `}` character to show the end of the placeholder.
// Formatting styles, and their effects are as follows:
// - `s`: Converts the value to a string with `str()` to display. This is very generic.
// - `i` or `d`: Formats numeric values as integers.
// - `f`: Formats numeric values with the precision number of digits after the decimal point. NaN and Inf are shown as `nan` and `inf`.
// - `F`: Formats numeric values with the precision number of digits after the decimal point. NaN and Inf are shown as `NAN` and `INF`.
// - `g`: Formats numeric values with the precision number of total significant digits. NaN and Inf are shown as `nan` and `inf`. Mantissas are demarked by `e`.
// - `G`: Formats numeric values with the precision number of total significant digits. NaN and Inf are shown as `NAN` and `INF`. Mantissas are demarked by `E`.
// - `b`: If the value logically evaluates as true, it shows as `true`, otherwise `false`.
// - `B`: If the value logically evaluates as true, it shows as `TRUE`, otherwise `FALSE`.
// Arguments:
// fmt = The formatting string, with placeholders to format the values into.
// vals = The list of values to format.
// use_nbsp = Pad fields with HTML entity `&nbsp;` instead of spaces.
// Example(NORENDER):
// str_format("The value of {} is {:.14f}.", ["pi", PI]); // Returns: "The value of pi is 3.14159265358979."
// str_format("The value {1:f} is known as {0}.", ["pi", PI]); // Returns: "The value 3.141593 is known as pi."
// str_format("We use a very small value {1:.6g} as {0}.", ["EPSILON", EPSILON]); // Returns: "We use a ver small value 1e-9 as EPSILON."
// str_format("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // Returns: "foo 12000true"
// str_format("{:-10s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostamus27.440"
// str_format("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // Returns: "plecostam 27.440"
function str_format(fmt, vals, use_nbsp=false) =
let(
parts = str_split(fmt,"{")
) str_join([
for(i = idx(parts))
let(
found_brace = i==0 || [for (c=parts[i]) if(c=="}") c] != [],
err = assert(found_brace, "Unbalanced { in format string."),
p = i==0? [undef,parts[i]] : str_split(parts[i],"}"),
fmta = p[0],
raw = p[1]
) each [
assert(i<99)
is_undef(fmta)? "" : let(
fmtb = str_split(fmta,":"),
num = is_digit(fmtb[0])? str_int(fmtb[0]) : (i-1),
left = fmtb[1][0] == "-",
fmtb1 = default(fmtb[1],""),
fmtc = left? substr(fmtb1,1) : fmtb1,
zero = fmtc[0] == "0",
lch = fmtc==""? "" : fmtc[len(fmtc)-1],
hastyp = is_letter(lch),
typ = hastyp? lch : "s",
fmtd = hastyp? substr(fmtc,0,len(fmtc)-1) : fmtc,
fmte = str_split((zero? substr(fmtd,1) : fmtd), "."),
wid = str_int(fmte[0]),
prec = str_int(fmte[1]),
val = assert(num>=0&&num<len(vals)) vals[num],
unpad = typ=="s"? (
let( sval = str(val) )
is_undef(prec)? sval :
substr(sval, 0, min(len(sval)-1, prec))
) :
(typ=="d" || typ=="i")? fmt_int(val) :
typ=="b"? (val? "true" : "false") :
typ=="B"? (val? "TRUE" : "FALSE") :
typ=="f"? downcase(fmt_fixed(val,default(prec,6))) :
typ=="F"? upcase(fmt_fixed(val,default(prec,6))) :
typ=="g"? downcase(fmt_float(val,default(prec,6))) :
typ=="G"? upcase(fmt_float(val,default(prec,6))) :
assert(false,str("Unknown format type: ",typ)),
padlen = max(0,wid-len(unpad)),
padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : use_nbsp? "&nbsp;" : " "]),
out = left? str(unpad, padfill) : str(padfill, unpad)
)
out, raw
]
]);
// Function&Module: echofmt()
// Usage:
// echofmt(fmt,vals);
// Description:
// Formats the given `vals` with the given format string `fmt` using [`str_format()`](#str_format), and echos the resultant string.
// Arguments:
// fmt = The formatting string, with placeholders to format the values into.
// vals = The list of values to format.
// use_nbsp = Pad fields with HTML entity `&nbsp;` instead of spaces.
// Example(NORENDER):
// echofmt("The value of {} is {:.14f}.", ["pi", PI]); // ECHO: "The value of pi is 3.14159265358979."
// echofmt("The value {1:f} is known as {0}.", ["pi", PI]); // ECHO: "The value 3.141593 is known as pi."
// echofmt("We use a very small value {1:.6g} as {0}.", ["EPSILON", EPSILON]); // ECHO: "We use a ver small value 1e-9 as EPSILON."
// echofmt("{:-5s}{:i}{:b}", ["foo", 12e3, 5]); // ECHO: "foo 12000true"
// echofmt("{:-10s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostamus27.440"
// echofmt("{:-10.9s}{:.3f}", ["plecostamus",27.43982]); // ECHO: "plecostam 27.440"
function echofmt(fmt, vals, use_nbsp=false) = echo(str_format(fmt,vals,use_nbsp));
module echofmt(fmt, vals, use_nbsp=false) echo(str_format(fmt,vals,use_nbsp));
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -8,7 +8,7 @@
//////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,186];
BOSL_VERSION = [2,0,187];
// Section: BOSL Library Version Functions