return [expr {[dict size $o_data] - 1}] ;#return index of item
}
method remove {idx {endRange ""}} {
if {[string length $endRange]} {
error "[self object] collection error: ranged removal not yet implemented.. remove one item at a time"
}
if {[string is integer -strict $idx]} {
if {$idx < 0} {
set idx "end-[expr {abs($idx+1)}]"
}
set key [lindex [dict keys $o_data] $idx]
set posn $idx
} else {
set key $idx
set posn [lsearch -exact [dict keys $o_data] $key]
if {$posn < 0} {
error "[self object] no such index: '$idx' in this collection"
}
}
dict unset o_data $key
return
}
method clear {} {
set o_data [dict create]
return
}
method reverse_the_collection {} {
#named slightly obtusely because reversing the data when there may be references held is a potential source of bugs
#the name reverse_the_collection should make it clear that the object is being modified in place as opposed to simply 'reverse' which may imply a view/copy.
#todo - consider implementing a get_reverse which provides an interface to the same collection without affecting original references, yet both allowing delete/edit operations.
#as a common case optimisation - it will not merge a single element list, even if that code contains redundant elements
#as a common case optimisation - it will not merge a single element list, even if that code contains redundant elements
proc sgr_merge_list {args} {
proc sgr_merge_list {args} {
if {[llength $args] == 0} {
if {[llength $args] == 0} {
@ -1755,9 +1756,30 @@ namespace eval punk::ansi {
} elseif {[llength $args] == 1} {
} elseif {[llength $args] == 1} {
return [lindex $args 0]
return [lindex $args 0]
}
}
sgr_merge $args
}
#codes *must* already have been split so that one esc per element in codelist
#e.g codelist [a+ Yellow Red underline] [a+ blue] [a+ red] is ok
#but codelist "[a+ Yellow Red underline][a+ blue]" [a+ red] is not
#(use punk::ansi::ta::split_codes_single)
proc sgr_merge {codelist args} {
variable codestate_empty
variable codestate_empty
set othercodes [list]
set othercodes [list]
set defaults [dict create\
-filter_fg 0\
-filter_bg 0\
]
dict for {k v} $args {
switch -- $k {
-filter_fg - -filter_bg {}
default {
error "sgr_merge unknown option '$k'. Known options [dict keys $defaults]"
}
}
}
set opts [dict merge $defaults $args]
set codestate $codestate_empty
set codestate $codestate_empty
set codestate_initial $codestate_empty ;#keep a copy for resets.
set codestate_initial $codestate_empty ;#keep a copy for resets.
set did_reset 0
set did_reset 0
@ -1772,7 +1794,7 @@ namespace eval punk::ansi {
#We still output any non SGR codes in the list as they came in - preserving their CSI
#We still output any non SGR codes in the list as they came in - preserving their CSI
foreach c $args {
foreach c $codelist {
#normalize 8bit to a token of the same length so our string operations on the code are the same and we can maintain a switch statement with literals rather than escapes
#normalize 8bit to a token of the same length so our string operations on the code are the same and we can maintain a switch statement with literals rather than escapes
#[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
#[para]Users may define procs which accept a list 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 -1 if the supplied index expression is out of bounds for the supplied list.
#[para]Otherwise it will return an integer corresponding to the position in the list.
#[para]Like Tcl list commands - it will produce an error if the form of the
#Note that for an index such as $x+1 - we never see the '$x' as it is substituted in the calling command. We will get something like 10+1 - which we will resolve (hopefully safely) with expr
#Note that for an index such as $x+1 - we never see the '$x' as it is substituted in the calling command. We will get something like 10+1 - which we will resolve (hopefully safely) with expr
if {![llength $list]} {
if {![llength $list]} {
return -1
return -1
@ -271,7 +282,7 @@ namespace eval punk::lib {
}
}
}
}
}
}
proc list_index_resolve2 {list index} {
proc lindex_resolve2 {list index} {
set indices [list] ;#building this may be somewhat expensive in terms of storage and compute for large lists - we could use lseq in Tcl 8.7+ but that's likely unavailable here.
set indices [list] ;#building this may be somewhat expensive in terms of storage and compute for large lists - we could use lseq in Tcl 8.7+ but that's likely unavailable here.
<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>
<dd><p>return the leading portion of rawbytes that is a valid utf8 sequence.</p>
<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>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>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>
@ -229,26 +239,26 @@
<p>Note that while this will return valid utf8 - it has no knowledge of grapheme clusters or diacritics</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>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>
<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>
<p>This uses a 'live' proc body - the divisor for the change of base is computed once at definition time</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 <ahref="https://wiki.tcl-lang.org/page/Additional+math+functions">https://wiki.tcl-lang.org/page/Additional+math+functions</a>)</p></dd>
<p>(courtesy of RS <ahref="https://wiki.tcl-lang.org/page/Additional+math+functions">https://wiki.tcl-lang.org/page/Additional+math+functions</a>)</p></dd>
<dd><p>Return a sorted list of the positive factors of x where x > 0</p>
<dd><p>Return a sorted list of the positive factors of x where x > 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>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>This is a simple brute-force implementation that iterates all numbers below the square root of x to check the factors</p>
@ -261,42 +271,42 @@ but has the disadvantage of being slower for 'small' numbers and using more memo
<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>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>* 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>
<p>In other mathematical contexts zero may be considered not to divide anything.</p></dd>
<dd><p>Return a boolean indicating whether str contains any of the glob characters: * ? [ ]</p>
<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>
<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>
<dd><p>The standard dict merge accepts multiple dicts with values from dicts to the right (2nd argument) taking precedence.</p>
<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>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>
<p>This function merges the two dicts whilst maintaining the key order of main followed by defaults.</p></dd>
<dd><p>This simply joines the elements of the list with -joinchar</p>
<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 <le></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 <le></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>
<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><aname="26"><bclass="function">lines_as_list</b><spanclass="opt">?option value ...?</span><iclass="arg">text</i></a></dt>
<dt><aname="27"><bclass="function">lines_as_list</b><spanclass="opt">?option value ...?</span><iclass="arg">text</i></a></dt>
<dd><p>Returns a list of possibly trimmed lines depeding on options</p>
<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>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>
<p>- not console lines which may be entirely different due to control characters such as vertical tabs or ANSI movements</p></dd>
#as a common case optimisation - it will not merge a single element list, even if that code contains redundant elements
#as a common case optimisation - it will not merge a single element list, even if that code contains redundant elements
proc sgr_merge_list {args} {
proc sgr_merge_list {args} {
if {[llength $args] == 0} {
if {[llength $args] == 0} {
@ -1755,9 +1756,30 @@ namespace eval punk::ansi {
} elseif {[llength $args] == 1} {
} elseif {[llength $args] == 1} {
return [lindex $args 0]
return [lindex $args 0]
}
}
sgr_merge $args
}
#codes *must* already have been split so that one esc per element in codelist
#e.g codelist [a+ Yellow Red underline] [a+ blue] [a+ red] is ok
#but codelist "[a+ Yellow Red underline][a+ blue]" [a+ red] is not
#(use punk::ansi::ta::split_codes_single)
proc sgr_merge {codelist args} {
variable codestate_empty
variable codestate_empty
set othercodes [list]
set othercodes [list]
set defaults [dict create\
-filter_fg 0\
-filter_bg 0\
]
dict for {k v} $args {
switch -- $k {
-filter_fg - -filter_bg {}
default {
error "sgr_merge unknown option '$k'. Known options [dict keys $defaults]"
}
}
}
set opts [dict merge $defaults $args]
set codestate $codestate_empty
set codestate $codestate_empty
set codestate_initial $codestate_empty ;#keep a copy for resets.
set codestate_initial $codestate_empty ;#keep a copy for resets.
set did_reset 0
set did_reset 0
@ -1772,7 +1794,7 @@ namespace eval punk::ansi {
#We still output any non SGR codes in the list as they came in - preserving their CSI
#We still output any non SGR codes in the list as they came in - preserving their CSI
foreach c $args {
foreach c $codelist {
#normalize 8bit to a token of the same length so our string operations on the code are the same and we can maintain a switch statement with literals rather than escapes
#normalize 8bit to a token of the same length so our string operations on the code are the same and we can maintain a switch statement with literals rather than escapes
#[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
#[para]Users may define procs which accept a list 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 -1 if the supplied index expression is out of bounds for the supplied list.
#[para]Otherwise it will return an integer corresponding to the position in the list.
#[para]Like Tcl list commands - it will produce an error if the form of the index is not acceptable
#Note that for an index such as $x+1 - we never see the '$x' as it is substituted in the calling command. We will get something like 10+1 - which we will resolve (hopefully safely) with expr
#Note that for an index such as $x+1 - we never see the '$x' as it is substituted in the calling command. We will get something like 10+1 - which we will resolve (hopefully safely) with expr
if {![llength $list]} {
if {![llength $list]} {
return -1
return -1
@ -271,7 +282,7 @@ namespace eval punk::lib {
}
}
}
}
}
}
proc list_index_resolve2 {list index} {
proc lindex_resolve2 {list index} {
set indices [list] ;#building this may be somewhat expensive in terms of storage and compute for large lists - we could use lseq in Tcl 8.7+ but that's likely unavailable here.
set indices [list] ;#building this may be somewhat expensive in terms of storage and compute for large lists - we could use lseq in Tcl 8.7+ but that's likely unavailable here.