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.
 
 
 
 
 
 

369 lines
23 KiB

<!DOCTYPE html><html><head>
<title>punkshell_module_punk::lib - punk library</title>
<style type="text/css"><!--
HTML {
background: #FFFFFF;
color: black;
}
BODY {
background: #FFFFFF;
color: black;
}
DIV.doctools {
margin-left: 10%;
margin-right: 10%;
}
DIV.doctools H1,DIV.doctools H2 {
margin-left: -5%;
}
H1, H2, H3, H4 {
margin-top: 1em;
font-family: sans-serif;
font-size: large;
color: #005A9C;
background: transparent;
text-align: left;
}
H1.doctools_title {
text-align: center;
}
UL,OL {
margin-right: 0em;
margin-top: 3pt;
margin-bottom: 3pt;
}
UL LI {
list-style: disc;
}
OL LI {
list-style: decimal;
}
DT {
padding-top: 1ex;
}
UL.doctools_toc,UL.doctools_toc UL, UL.doctools_toc UL UL {
font: normal 12pt/14pt sans-serif;
list-style: none;
}
LI.doctools_section, LI.doctools_subsection {
list-style: none;
margin-left: 0em;
text-indent: 0em;
padding: 0em;
}
PRE {
display: block;
font-family: monospace;
white-space: pre;
margin: 0%;
padding-top: 0.5ex;
padding-bottom: 0.5ex;
padding-left: 1ex;
padding-right: 1ex;
width: 100%;
}
PRE.doctools_example {
color: black;
background: #f5dcb3;
border: 1px solid black;
}
UL.doctools_requirements LI, UL.doctools_syntax LI {
list-style: none;
margin-left: 0em;
text-indent: 0em;
padding: 0em;
}
DIV.doctools_synopsis {
color: black;
background: #80ffff;
border: 1px solid black;
font-family: serif;
margin-top: 1em;
margin-bottom: 1em;
}
UL.doctools_syntax {
margin-top: 1em;
border-top: 1px solid black;
}
UL.doctools_requirements {
margin-bottom: 1em;
border-bottom: 1px solid black;
}
--></style>
</head>
<!-- Generated from file '_module_lib-0.1.1.tm.man' by tcllib/doctools with format 'html'
-->
<!-- Copyright &amp;copy; 2024
-->
<!-- punkshell_module_punk::lib.0
-->
<body><hr> [
<a href="../../../toc.html">Main Table Of Contents</a>
&#124; <a href="../../toc.html">Table Of Contents</a>
&#124; <a href="../../../index.html">Keyword Index</a>
] <hr>
<div class="doctools">
<h1 class="doctools_title">punkshell_module_punk::lib(0) 0.1.1 doc &quot;punk library&quot;</h1>
<div id="name" class="doctools_section"><h2><a name="name">Name</a></h2>
<p>punkshell_module_punk::lib - punk general utility functions</p>
</div>
<div id="toc" class="doctools_section"><h2><a name="toc">Table Of Contents</a></h2>
<ul class="doctools_toc">
<li class="doctools_section"><a href="#toc">Table Of Contents</a></li>
<li class="doctools_section"><a href="#synopsis">Synopsis</a></li>
<li class="doctools_section"><a href="#section1">Description</a></li>
<li class="doctools_section"><a href="#section2">Overview</a>
<ul>
<li class="doctools_subsection"><a href="#subsection1">Concepts</a></li>
<li class="doctools_subsection"><a href="#subsection2">dependencies</a></li>
</ul>
</li>
<li class="doctools_section"><a href="#section3">API</a>
<ul>
<li class="doctools_subsection"><a href="#subsection3">Namespace punk::lib::class</a></li>
<li class="doctools_subsection"><a href="#subsection4">Namespace punk::lib::compat</a></li>
<li class="doctools_subsection"><a href="#subsection5">Namespace punk::lib</a></li>
</ul>
</li>
<li class="doctools_section"><a href="#section4">Internal</a>
<ul>
<li class="doctools_subsection"><a href="#subsection6">Namespace punk::lib::system</a></li>
</ul>
</li>
<li class="doctools_section"><a href="#keywords">Keywords</a></li>
<li class="doctools_section"><a href="#copyright">Copyright</a></li>
</ul>
</div>
<div id="synopsis" class="doctools_section"><h2><a name="synopsis">Synopsis</a></h2>
<div class="doctools_synopsis">
<ul class="doctools_requirements">
<li>package require <b class="pkgname">punk::lib</b></li>
</ul>
<ul class="doctools_syntax">
<li><a href="#1"><b class="function">lremove</b> <i class="arg">list</i> <span class="opt">?index ...?</span></a></li>
<li><a href="#2"><b class="function">lpop</b> <i class="arg">listvar</i> <span class="opt">?index?</span></a></li>
<li><a href="#3"><b class="function">lindex_resolve</b> <i class="arg">list</i> <i class="arg">index</i></a></li>
<li><a href="#4"><b class="function">K</b> <i class="arg">x</i> <i class="arg">y</i></a></li>
<li><a href="#5"><b class="function">is_utf8_multibyteprefix</b> <i class="arg">str</i></a></li>
<li><a href="#6"><b class="function">is_utf8_single</b> <i class="arg">1234bytes</i></a></li>
<li><a href="#7"><b class="function">get_utf8_leading</b> <i class="arg">rawbytes</i></a></li>
<li><a href="#8"><b class="function">hex2dec</b> <span class="opt">?option value...?</span> <i class="arg">list_largeHex</i></a></li>
<li><a href="#9"><b class="function">dex2hex</b> <span class="opt">?option value...?</span> <i class="arg">list_decimals</i></a></li>
<li><a href="#10"><b class="function">log2</b> <i class="arg">x</i></a></li>
<li><a href="#11"><b class="function">logbase</b> <i class="arg">b</i> <i class="arg">x</i></a></li>
<li><a href="#12"><b class="function">factors</b> <i class="arg">x</i></a></li>
<li><a href="#13"><b class="function">oddFactors</b> <i class="arg">x</i></a></li>
<li><a href="#14"><b class="function">greatestFactorBelow</b> <i class="arg">x</i></a></li>
<li><a href="#15"><b class="function">greatestOddFactorBelow</b> <i class="arg">x</i></a></li>
<li><a href="#16"><b class="function">greatestOddFactor</b> <i class="arg">x</i></a></li>
<li><a href="#17"><b class="function">gcd</b> <i class="arg">n</i> <i class="arg">m</i></a></li>
<li><a href="#18"><b class="function">gcd</b> <i class="arg">n</i> <i class="arg">m</i></a></li>
<li><a href="#19"><b class="function">commonDivisors</b> <i class="arg">x</i> <i class="arg">y</i></a></li>
<li><a href="#20"><b class="function">hasglobs</b> <i class="arg">str</i></a></li>
<li><a href="#21"><b class="function">trimzero</b> <i class="arg">number</i></a></li>
<li><a href="#22"><b class="function">substring_count</b> <i class="arg">str</i> <i class="arg">substring</i></a></li>
<li><a href="#23"><b class="function">dict_merge_ordered</b> <i class="arg">defaults</i> <i class="arg">main</i></a></li>
<li><a href="#24"><b class="function">askuser</b> <i class="arg">question</i></a></li>
<li><a href="#25"><b class="function">linesort</b> <span class="opt">?sortoption ?val?...?</span> <i class="arg">textblock</i></a></li>
<li><a href="#26"><b class="function">list_as_lines</b> <span class="opt">?-joinchar char?</span> <i class="arg">linelist</i></a></li>
<li><a href="#27"><b class="function">lines_as_list</b> <span class="opt">?option value ...?</span> <i class="arg">text</i></a></li>
<li><a href="#28"><b class="function">opts_values</b> <span class="opt">?option value...?</span> <i class="arg">optionspecs</i> <i class="arg">rawargs</i></a></li>
</ul>
</div>
</div>
<div id="section1" class="doctools_section"><h2><a name="section1">Description</a></h2>
<p>This is a set of utility functions that are commonly used across punk modules or are just considered to be general-purpose functions.</p>
<p>The base set includes string and math functions but has no specific theme</p>
</div>
<div id="section2" class="doctools_section"><h2><a name="section2">Overview</a></h2>
<p>overview of punk::lib</p>
<div id="subsection1" class="doctools_subsection"><h3><a name="subsection1">Concepts</a></h3>
<p>The punk::lib modules should have no strong dependencies other than Tcl</p>
<p>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.</p>
<p>This requirement for no strong dependencies, means that many utility functions that might otherwise seem worthy of inclusion here are not present.</p>
</div>
<div id="subsection2" class="doctools_subsection"><h3><a name="subsection2">dependencies</a></h3>
<p>packages used by punk::lib</p>
<ul class="doctools_itemized">
<li><p><b class="package">Tcl 8.6-</b></p></li>
</ul>
</div>
</div>
<div id="section3" class="doctools_section"><h2><a name="section3">API</a></h2>
<div id="subsection3" class="doctools_subsection"><h3><a name="subsection3">Namespace punk::lib::class</a></h3>
<p>class definitions</p>
<ol class="doctools_enumerated">
</ol>
</div>
<div id="subsection4" class="doctools_subsection"><h3><a name="subsection4">Namespace punk::lib::compat</a></h3>
<p>compatibility functions for features that may not be available in earlier Tcl versions</p>
<p>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.</p>
<p>Such Tcl-only versions will inevitably be less performant - perhaps significantly so.</p>
<dl class="doctools_definitions">
<dt><a name="1"><b class="function">lremove</b> <i class="arg">list</i> <span class="opt">?index ...?</span></a></dt>
<dd><p>Forwards compatible lremove for versions 8.6 or less to support equivalent 8.7 lremove</p></dd>
<dt><a name="2"><b class="function">lpop</b> <i class="arg">listvar</i> <span class="opt">?index?</span></a></dt>
<dd><p>Forwards compatible lpop for versions 8.6 or less to support equivalent 8.7 lpop</p></dd>
</dl>
</div>
<div id="subsection5" class="doctools_subsection"><h3><a name="subsection5">Namespace punk::lib</a></h3>
<p>Core API functions for punk::lib</p>
<dl class="doctools_definitions">
<dt><a name="3"><b class="function">lindex_resolve</b> <i class="arg">list</i> <i class="arg">index</i></a></dt>
<dd><p>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</p>
<p>Users may define procs which accept a list index and wish to accept the forms understood by Tcl.</p>
<p>This means the proc may be called with something like $x+2 end-$y etc</p>
<p>Sometimes the actual integer index is desired.</p>
<p>We want to resolve the index used, without passing arbitrary expressions into the 'expr' function - which could have security risks.</p>
<p>lindex_resolve will parse the index expression and return -1 if the supplied index expression is out of bounds for the supplied list.</p>
<p>Otherwise it will return an integer corresponding to the position in the list.</p>
<p>Like Tcl list commands - it will produce an error if the form of the</p></dd>
<dt><a name="4"><b class="function">K</b> <i class="arg">x</i> <i class="arg">y</i></a></dt>
<dd><p>The K-combinator function - returns the first argument, x and discards y</p>
<p>see <a href="https://wiki.tcl-lang.org/page/K">https://wiki.tcl-lang.org/page/K</a></p>
<p>It is used in cases where command-substitution at the calling-point performs some desired effect.</p></dd>
<dt><a name="5"><b class="function">is_utf8_multibyteprefix</b> <i class="arg">str</i></a></dt>
<dd><p>Returns a boolean if str is potentially a prefix for a multibyte utf-8 character</p>
<p>ie - tests if it is possible that appending more data will result in a utf-8 codepoint</p>
<p>Will return false for an already complete utf-8 codepoint</p>
<p>It is assumed the incomplete sequence is at the beginning of the bytes argument</p>
<p>Suitable input for this might be from the unreturned tail portion of get_utf8_leading $testbytes</p>
<p>e.g using: set head [get_utf8_leading $testbytes] ; set tail [string range $testbytes [string length $head] end]</p></dd>
<dt><a name="6"><b class="function">is_utf8_single</b> <i class="arg">1234bytes</i></a></dt>
<dd><p>Tests input of 1,2,3 or 4 bytes and responds with a boolean indicating if it is a valid utf-8 character (codepoint)</p></dd>
<dt><a name="7"><b class="function">get_utf8_leading</b> <i class="arg">rawbytes</i></a></dt>
<dd><p>return the leading portion of rawbytes that is a valid utf8 sequence.</p>
<p>This will stop at the point at which the bytes can't be interpreted as a complete utf-8 codepoint</p>
<p>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.</p>
<p>It will also only return the prefix before any bytes that cannot be part of a utf-8 sequence at all.</p>
<p>Note that while this will return valid utf8 - it has no knowledge of grapheme clusters or diacritics</p>
<p>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</p>
<p>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</p></dd>
<dt><a name="8"><b class="function">hex2dec</b> <span class="opt">?option value...?</span> <i class="arg">list_largeHex</i></a></dt>
<dd><p>Convert a list of (possibly large) unprefixed hex strings to their decimal values</p>
<p>hex2dec accepts and ignores internal underscores in the same manner as Tcl 8.7+ numbers e.g hex2dec FF_FF returns 65535</p>
<p>Leading and trailing underscores are ignored as a matter of implementation convenience - but this shouldn't be relied upon.</p>
<p>Leading or trailing whitespace in each list member is allowed e.g hex2dec &quot; F&quot; returns 15</p>
<p>Internal whitespace e.g &quot;F F&quot; is not permitted - but a completely empty element &quot;&quot; is allowed and will return 0</p></dd>
<dt><a name="9"><b class="function">dex2hex</b> <span class="opt">?option value...?</span> <i class="arg">list_decimals</i></a></dt>
<dd><p>Convert a list of decimal integers to a list of hex values</p>
<p>-width &lt;int&gt; can be used to make each hex value at least int characters wide, with leading zeroes.</p>
<p>-case upper|lower determines the case of the hex letters in the output</p></dd>
<dt><a name="10"><b class="function">log2</b> <i class="arg">x</i></a></dt>
<dd><p>log base2 of x</p>
<p>This uses a 'live' proc body - the divisor for the change of base is computed once at definition time</p>
<p>(courtesy of RS <a href="https://wiki.tcl-lang.org/page/Additional+math+functions">https://wiki.tcl-lang.org/page/Additional+math+functions</a>)</p></dd>
<dt><a name="11"><b class="function">logbase</b> <i class="arg">b</i> <i class="arg">x</i></a></dt>
<dd><p>log base b of x</p>
<p>This function uses expr's natural log and the change of base division.</p>
<p>This means for example that we can get results like: logbase 10 1000 = 2.9999999999999996</p>
<p>Use expr's log10() function or tcl::mathfunc::log10 for base 10</p></dd>
<dt><a name="12"><b class="function">factors</b> <i class="arg">x</i></a></dt>
<dd><p>Return a sorted list of the positive factors of x where x &gt; 0</p>
<p>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)*</p>
<p>This is a simple brute-force implementation that iterates all numbers below the square root of x to check the factors</p>
<p>Because the implementation is so simple - the performance is very reasonable for numbers below at least a few 10's of millions</p>
<p>See tcllib math::numtheory::factors for a more complex implementation - which seems to be slower for 'small' numbers</p>
<p>Comparisons were done with some numbers below 17 digits long</p>
<p>For seriously big numbers - this simple algorithm would no doubt be outperformed by more complex algorithms.</p>
<p>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.</p>
<p>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</p>
<p>* Taking x=0; Notion of x being divisible by integer y being: There exists an integer p such that x = py</p>
<p>In other mathematical contexts zero may be considered not to divide anything.</p></dd>
<dt><a name="13"><b class="function">oddFactors</b> <i class="arg">x</i></a></dt>
<dd><p>Return a list of odd integer factors of x, sorted in ascending order</p></dd>
<dt><a name="14"><b class="function">greatestFactorBelow</b> <i class="arg">x</i></a></dt>
<dd><p>Return the largest factor of x excluding itself</p>
<p>factor functions can be useful for console layout calculations</p>
<p>See Tcllib math::numtheory for more extensive implementations</p></dd>
<dt><a name="15"><b class="function">greatestOddFactorBelow</b> <i class="arg">x</i></a></dt>
<dd><p>Return the largest odd integer factor of x excluding x itself</p></dd>
<dt><a name="16"><b class="function">greatestOddFactor</b> <i class="arg">x</i></a></dt>
<dd><p>Return the largest odd integer factor of x</p>
<p>For an odd value of x - this will always return x</p></dd>
<dt><a name="17"><b class="function">gcd</b> <i class="arg">n</i> <i class="arg">m</i></a></dt>
<dd><p>Return the greatest common divisor of m and n</p>
<p>Straight from Lars Hellstr&ouml;m's math::numtheory library in Tcllib</p>
<p>Graphical use:</p>
<p>An a by b rectangle can be covered with square tiles of side-length c,</p>
<p>only if c is a common divisor of a and b</p></dd>
<dt><a name="18"><b class="function">gcd</b> <i class="arg">n</i> <i class="arg">m</i></a></dt>
<dd><p>Return the lowest common multiple of m and n</p>
<p>Straight from Lars Hellstr&ouml;m's math::numtheory library in Tcllib</p></dd>
<dt><a name="19"><b class="function">commonDivisors</b> <i class="arg">x</i> <i class="arg">y</i></a></dt>
<dd><p>Return a list of all the common factors of x and y</p>
<p>(equivalent to factors of their gcd)</p></dd>
<dt><a name="20"><b class="function">hasglobs</b> <i class="arg">str</i></a></dt>
<dd><p>Return a boolean indicating whether str contains any of the glob characters: * ? [ ]</p>
<p>hasglobs uses append to preserve Tcls internal representation for str - so it should help avoid shimmering in the few cases where this may matter.</p></dd>
<dt><a name="21"><b class="function">trimzero</b> <i class="arg">number</i></a></dt>
<dd><p>Return number with left-hand-side zeros trimmed off - unless all zero</p>
<p>If number is all zero - a single 0 is returned</p></dd>
<dt><a name="22"><b class="function">substring_count</b> <i class="arg">str</i> <i class="arg">substring</i></a></dt>
<dd><p>Search str and return number of occurrences of substring</p></dd>
<dt><a name="23"><b class="function">dict_merge_ordered</b> <i class="arg">defaults</i> <i class="arg">main</i></a></dt>
<dd><p>The standard dict merge accepts multiple dicts with values from dicts to the right (2nd argument) taking precedence.</p>
<p>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.</p>
<p>This function merges the two dicts whilst maintaining the key order of main followed by defaults.</p></dd>
<dt><a name="24"><b class="function">askuser</b> <i class="arg">question</i></a></dt>
<dd><p>A basic utility to read an answer from stdin</p>
<p>The prompt is written to the terminal and then it waits for a user to type something</p>
<p>stdin is temporarily configured to blocking and then put back in its original state in case it wasn't already so.</p>
<p>If the terminal is using punk::console and is in raw mode - the terminal will temporarily be put in line mode.</p>
<p>(Generic terminal raw vs linemode detection not yet present)</p>
<p>The user must hit enter to submit the response</p>
<p>The return value is the string if any that was typed prior to hitting enter.</p>
<p>The question argument can be manually colourised using the various punk::ansi funcitons</p>
<pre class="doctools_example">
set answer [punk::lib::askuser &quot;[a+ green bold]Do you want to proceed? (Y|N)[a]&quot;]
if {[string match y* [string tolower $answer]]} {
puts &quot;Proceeding&quot;
} else {
puts &quot;Cancelled by user&quot;
}
</pre>
</dd>
<dt><a name="25"><b class="function">linesort</b> <span class="opt">?sortoption ?val?...?</span> <i class="arg">textblock</i></a></dt>
<dd><p>Sort lines in textblock</p>
<p>Returns another textblock with lines sorted</p>
<p>options are flags as accepted by lsort ie -ascii -command -decreasing -dictionary -index -indices -integer -nocase -real -stride -unique</p></dd>
<dt><a name="26"><b class="function">list_as_lines</b> <span class="opt">?-joinchar char?</span> <i class="arg">linelist</i></a></dt>
<dd><p>This simply joines the elements of the list with -joinchar</p>
<p>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 &lt;le&gt;</p>
<p>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.</p></dd>
<dt><a name="27"><b class="function">lines_as_list</b> <span class="opt">?option value ...?</span> <i class="arg">text</i></a></dt>
<dd><p>Returns a list of possibly trimmed lines depeding on options</p>
<p>The concept of lines is raw lines from splitting on newline after crlf is mapped to lf</p>
<p>- not console lines which may be entirely different due to control characters such as vertical tabs or ANSI movements</p></dd>
<dt><a name="28"><b class="function">opts_values</b> <span class="opt">?option value...?</span> <i class="arg">optionspecs</i> <i class="arg">rawargs</i></a></dt>
<dd><p>Parse rawargs as a sequence of zero or more option-value pairs followed by zero or more values</p>
<p>Returns a dict of the form: opts &lt;options_dict&gt; values &lt;values_dict&gt;</p>
<p>ARGUMENTS:</p>
<dl class="doctools_arguments">
<dt>multiline-string <i class="arg">optionspecs</i></dt>
<dd><p>This a block of text with records delimited by newlines (lf or crlf) - but with multiline values allowed if properly quoted/braced</p>
<p>'info complete' is used to determine if a record spans multiple lines due to multiline values</p>
<p>Each optionspec line must be of the form:</p>
<p>-optionname -key val -key2 val2...</p>
<p>where the valid keys for each option specification are: -default -type -range -choices -optional</p></dd>
<dt>list <i class="arg">rawargs</i></dt>
<dd><p>This is a list of the arguments to parse. Usually it will be the \$args value from the containing proc</p></dd>
</dl></dd>
</dl>
</div>
</div>
<div id="section4" class="doctools_section"><h2><a name="section4">Internal</a></h2>
<div id="subsection6" class="doctools_subsection"><h3><a name="subsection6">Namespace punk::lib::system</a></h3>
<p>Internal functions that are not part of the API</p>
<dl class="doctools_definitions">
</dl>
</div>
</div>
<div id="keywords" class="doctools_section"><h2><a name="keywords">Keywords</a></h2>
<p><a href="../../../index.html#lib">lib</a>, <a href="../../../index.html#module">module</a>, <a href="../../../index.html#utility">utility</a></p>
</div>
<div id="copyright" class="doctools_section"><h2><a name="copyright">Copyright</a></h2>
<p>Copyright &copy; 2024</p>
</div>
</div></body></html>