You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
14 KiB
194 lines
14 KiB
[comment {--- punk::docgen generated from inline doctools comments ---}] |
|
[comment {--- punk::docgen DO NOT EDIT DOCS HERE UNLESS YOU REMOVE THESE COMMENT LINES ---}] |
|
[comment {--- punk::docgen overwrites this file ---}] |
|
[manpage_begin punkshell_module_punk::lib 0 0.1.2] |
|
[copyright "2024"] |
|
[titledesc {punk general utility functions}] [comment {-- Name section and table of contents description --}] |
|
[moddesc {punk library}] [comment {-- Description at end of page heading --}] |
|
[require punk::lib] |
|
[keywords module utility lib] |
|
[description] |
|
[para]This is a set of utility functions that are commonly used across punk modules or are just considered to be general-purpose functions. |
|
[para]The base set includes string and math functions but has no specific theme |
|
[section Overview] |
|
[para] overview of punk::lib |
|
[subsection Concepts] |
|
[para]The punk::lib modules should have no strong dependencies other than Tcl |
|
[para]Dependendencies that only affect display or additional functionality may be included - but should fail gracefully if not present, and only when a function is called that uses one of these soft dependencies. |
|
[para]This requirement for no strong dependencies, means that many utility functions that might otherwise seem worthy of inclusion here are not present. |
|
[subsection dependencies] |
|
[para] packages used by punk::lib |
|
[list_begin itemized] |
|
[item] [package {Tcl 8.6-}] |
|
[item] [package {punk::args}] |
|
[list_end] |
|
[section API] |
|
[subsection {Namespace punk::lib::compat}] |
|
[para] compatibility functions for features that may not be available in earlier Tcl versions |
|
[para] These are generally 'forward compatibility' functions ie allowing earlier versions to use later features/idioms by using a Tcl-only version of a missing builtin. |
|
[para] Such Tcl-only versions will inevitably be less performant - perhaps significantly so. |
|
[list_begin definitions] |
|
[call [fun lremove] [arg list] [opt {index ...}]] |
|
[para] Forwards compatible lremove for versions 8.6 or less to support equivalent 8.7 lremove |
|
[call [fun lpop] [arg listvar] [opt {index}]] |
|
[para] Forwards compatible lpop for versions 8.6 or less to support equivalent 8.7 lpop |
|
[list_end] [comment {--- end definitions namespace punk::lib::compat ---}] |
|
[subsection {Namespace punk::lib}] |
|
[para] Core API functions for punk::lib |
|
[list_begin definitions] |
|
[call [fun invoke] [arg command]] |
|
[para]Invoke an external command (using tcl open command) capturing stdout,stderr and the exitcode |
|
[example { |
|
set script { |
|
puts stdout {hello on stdout} |
|
puts stderr {hello on stderr} |
|
exit 42 |
|
} |
|
invoke [list tclsh <<$script] |
|
}] |
|
[call [fun lindex_resolve] [arg len] [arg index]] |
|
[para]Resolve an index which may be of the forms accepted by Tcl list commands such as end-2 or 2+2 to the actual integer index for the supplied list/string length |
|
[para]Users may define procs which accept a list/string index and wish to accept the forms understood by Tcl. |
|
[para]This means the proc may be called with something like $x+2 end-$y etc |
|
[para]Sometimes the actual integer index is desired. |
|
[para]We want to resolve the index used, without passing arbitrary expressions into the 'expr' function - which could have security risks. |
|
[para]lindex_resolve will parse the index expression and return: |
|
[para] a) -3 if the supplied index expression is below the lower bound for the supplied list. (< 0) |
|
[para] b) -2 if the supplied index expression is above the upper bound for the supplied list. (> end) |
|
[para] We don't return -1 - as the similar function lindex_resolve_basic uses this to denote out of range at either end of the list/string |
|
[para]Otherwise it will return an integer corresponding to the position in the list. |
|
[para]This is in stark contrast to Tcl list function indices which will return empty strings for out of bounds indices, or in the case of lrange, return results anyway. |
|
[para]Like Tcl list commands - it will produce an error if the form of the index is not acceptable |
|
[para]For empty lists, end and end+x indices are considered to be out of bounds on the upper side - thus returning -2 |
|
[call [fun lindex_resolve_basic] [arg len] [arg index]] |
|
[para] Accepts index of the forms accepted by Tcl's list commands. (e.g compound indices such as 3+1 end-2) |
|
[para] returns -1 for out of range at either end, or a valid integer index |
|
[para] Unlike lindex_resolve; lindex_resolve_basic can't determine if an out of range index was out of range at the lower or upper bound |
|
[para] This is only likely to be faster than average over lindex_resolve for small lists and for Tcl which has the builtin lseq command |
|
[para] The performance advantage is more likely to be present when using compound indexes such as $x+1 or end-1 |
|
[para] For pure integer indices the performance should be equivalent |
|
[call [fun K] [arg x] [arg y]] |
|
[para]The K-combinator function - returns the first argument, x and discards y |
|
[para]see [uri https://wiki.tcl-lang.org/page/K] |
|
[para]It is used in cases where command-substitution at the calling-point performs some desired effect. |
|
[call [fun is_utf8_multibyteprefix] [arg str]] |
|
[para] Returns a boolean if str is potentially a prefix for a multibyte utf-8 character |
|
[para] ie - tests if it is possible that appending more data will result in a utf-8 codepoint |
|
[para] Will return false for an already complete utf-8 codepoint |
|
[para] It is assumed the incomplete sequence is at the beginning of the bytes argument |
|
[para] Suitable input for this might be from the unreturned tail portion of get_utf8_leading $testbytes |
|
[para] e.g using: set head [lb]get_utf8_leading $testbytes[rb] ; set tail [lb]string range $testbytes [lb]string length $head[rb] end[rb] |
|
[call [fun is_utf8_single] [arg 1234bytes]] |
|
[para] Tests input of 1,2,3 or 4 bytes and responds with a boolean indicating if it is a valid utf-8 character (codepoint) |
|
[call [fun get_utf8_leading] [arg rawbytes]] |
|
[para] return the leading portion of rawbytes that is a valid utf8 sequence. |
|
[para] This will stop at the point at which the bytes can't be interpreted as a complete utf-8 codepoint |
|
[para] e.g It will not return the first byte or 2 of a 3-byte utf-8 character if the last byte is missing, and will return only the valid utf-8 string from before the first byte of the incomplete character. |
|
[para] It will also only return the prefix before any bytes that cannot be part of a utf-8 sequence at all. |
|
[para] Note that while this will return valid utf8 - it has no knowledge of grapheme clusters or diacritics |
|
[para] This means if it is being used to process bytes split at some arbitrary point - the trailing data that isn't returned could be part of a grapheme cluster that belongs with the last character of the leading string already returned |
|
[para] The utf-8 BOM \xEF\xBB\xBF is a valid UTF8 3-byte sequence and so can also be returned as part of the leading utf8 bytes |
|
[call [fun hex2dec] [opt {option value...}] [arg list_largeHex]] |
|
[para]Convert a list of (possibly large) unprefixed hex strings to their decimal values |
|
[para]hex2dec accepts and ignores internal underscores in the same manner as Tcl 8.7+ numbers e.g hex2dec FF_FF returns 65535 |
|
[para]Leading and trailing underscores are ignored as a matter of implementation convenience - but this shouldn't be relied upon. |
|
[para]Leading or trailing whitespace in each list member is allowed e.g hex2dec " F" returns 15 |
|
[para]Internal whitespace e.g "F F" is not permitted - but a completely empty element "" is allowed and will return 0 |
|
[call [fun dex2hex] [opt {option value...}] [arg list_decimals]] |
|
[para]Convert a list of decimal integers to a list of hex values |
|
[para] -width <int> can be used to make each hex value at least int characters wide, with leading zeroes. |
|
[para] -case upper|lower determines the case of the hex letters in the output |
|
[call [fun log2] [arg x]] |
|
[para]log base2 of x |
|
[para]This uses a 'live' proc body - the divisor for the change of base is computed once at definition time |
|
[para](courtesy of RS [uri https://wiki.tcl-lang.org/page/Additional+math+functions]) |
|
[call [fun logbase] [arg b] [arg x]] |
|
[para]log base b of x |
|
[para]This function uses expr's natural log and the change of base division. |
|
[para]This means for example that we can get results like: logbase 10 1000 = 2.9999999999999996 |
|
[para]Use expr's log10() function or tcl::mathfunc::log10 for base 10 |
|
[call [fun factors] [arg x]] |
|
[para]Return a sorted list of the positive factors of x where x > 0 |
|
[para]For x = 0 we return only 0 and 1 as technically any number divides zero and there are an infinite number of factors. (including zero itself in this context)* |
|
[para]This is a simple brute-force implementation that iterates all numbers below the square root of x to check the factors |
|
[para]Because the implementation is so simple - the performance is very reasonable for numbers below at least a few 10's of millions |
|
[para]See tcllib math::numtheory::factors for a more complex implementation - which seems to be slower for 'small' numbers |
|
[para]Comparisons were done with some numbers below 17 digits long |
|
[para]For seriously big numbers - this simple algorithm would no doubt be outperformed by more complex algorithms. |
|
[para]The numtheory library stores some data about primes etc with each call - so may become faster when being used on more numbers |
|
but has the disadvantage of being slower for 'small' numbers and using more memory. |
|
[para]If the largest factor below x is needed - the greatestOddFactorBelow and GreatestFactorBelow functions are a faster way to get there than computing the whole list, even for small values of x |
|
[para]* Taking x=0; Notion of x being divisible by integer y being: There exists an integer p such that x = py |
|
[para] In other mathematical contexts zero may be considered not to divide anything. |
|
[call [fun oddFactors] [arg x]] |
|
[para]Return a list of odd integer factors of x, sorted in ascending order |
|
[call [fun greatestFactorBelow] [arg x]] |
|
[para]Return the largest factor of x excluding itself |
|
[para]factor functions can be useful for console layout calculations |
|
[para]See Tcllib math::numtheory for more extensive implementations |
|
[call [fun greatestOddFactorBelow] [arg x]] |
|
[para]Return the largest odd integer factor of x excluding x itself |
|
[call [fun greatestOddFactor] [arg x]] |
|
[para]Return the largest odd integer factor of x |
|
[para]For an odd value of x - this will always return x |
|
[call [fun gcd] [arg n] [arg m]] |
|
[para]Return the greatest common divisor of m and n |
|
[para]Straight from Lars Hellström's math::numtheory library in Tcllib |
|
[para]Graphical use: |
|
[para]An a by b rectangle can be covered with square tiles of side-length c, |
|
[para]only if c is a common divisor of a and b |
|
[call [fun gcd] [arg n] [arg m]] |
|
[para]Return the lowest common multiple of m and n |
|
[para]Straight from Lars Hellström's math::numtheory library in Tcllib |
|
[para] |
|
[call [fun commonDivisors] [arg x] [arg y]] |
|
[para]Return a list of all the common factors of x and y |
|
[para](equivalent to factors of their gcd) |
|
[call [fun hasglobs] [arg str]] |
|
[para]Return a boolean indicating whether str contains any of the glob characters: * ? [lb] [rb] |
|
[para]hasglobs uses append to preserve Tcls internal representation for str - so it should help avoid shimmering in the few cases where this may matter. |
|
[call [fun trimzero] [arg number]] |
|
[para]Return number with left-hand-side zeros trimmed off - unless all zero |
|
[para]If number is all zero - a single 0 is returned |
|
[call [fun substring_count] [arg str] [arg substring]] |
|
[para]Search str and return number of occurrences of substring |
|
[call [fun dict_merge_ordered] [arg defaults] [arg main]] |
|
[para]The standard dict merge accepts multiple dicts with values from dicts to the right (2nd argument) taking precedence. |
|
[para]When merging with a dict of default values - this means that any default key/vals that weren't in the main dict appear in the output before the main data. |
|
[para]This function merges the two dicts whilst maintaining the key order of main followed by defaults. |
|
[call [fun askuser] [arg question]] |
|
[para]A basic utility to read an answer from stdin |
|
[para]The prompt is written to the terminal and then it waits for a user to type something |
|
[para]stdin is temporarily configured to blocking and then put back in its original state in case it wasn't already so. |
|
[para]If the terminal is using punk::console and is in raw mode - the terminal will temporarily be put in line mode. |
|
[para](Generic terminal raw vs linemode detection not yet present) |
|
[para]The user must hit enter to submit the response |
|
[para]The return value is the string if any that was typed prior to hitting enter. |
|
[para]The question argument can be manually colourised using the various punk::ansi funcitons |
|
[example_begin] |
|
set answer [lb]punk::lib::askuser "[lb]a+ green bold[rb]Do you want to proceed? (Y|N)[lb]a[rb]"[rb] |
|
if {[lb]string match y* [lb]string tolower $answer[rb][rb]} { |
|
puts "Proceeding" |
|
} else { |
|
puts "Cancelled by user" |
|
} |
|
[example_end] |
|
[call [fun linesort] [opt {sortoption ?val?...}] [arg textblock]] |
|
[para]Sort lines in textblock |
|
[para]Returns another textblock with lines sorted |
|
[para]options are flags as accepted by lsort ie -ascii -command -decreasing -dictionary -index -indices -integer -nocase -real -stride -unique |
|
[call [fun list_as_lines] [opt {-joinchar char}] [arg linelist]] |
|
[para]This simply joins the elements of the list with -joinchar |
|
[para]It is mainly intended for use in pipelines where the primary argument comes at the end - but it can also be used as a general replacement for join $lines <le> |
|
[para]The sister function lines_as_list takes a block of text and splits it into lines - but with more options related to trimming the block and/or each line. |
|
[call [fun lines_as_list] [opt {option value ...}] [arg text]] |
|
[para]Returns a list of possibly trimmed lines depeding on options |
|
[para]The concept of lines is raw lines from splitting on newline after crlf is mapped to lf |
|
[para]- not console lines which may be entirely different due to control characters such as vertical tabs or ANSI movements |
|
[list_end] [comment {--- end definitions namespace punk::lib ---}] |
|
[section Internal] |
|
[subsection {Namespace punk::lib::system}] |
|
[para] Internal functions that are not part of the API |
|
[list_begin definitions] |
|
[list_end] [comment {--- end definitions namespace punk::lib::system ---}] |
|
[manpage_end]
|
|
|