proc ladd {var args} { upvar $var v foreach val $args { if {$val ni $v} { lappend v $val } } } proc lremove {var val} { upvar $var v if {$val ni $v} return set v [lsearch -all -inline -not -exact $v $val] } # Returns the epoch time for midnight today, accounting for local timezone proc midnight {} {{today ""}} { if {$today eq ""} { set today [clock format [clock seconds] -format "%Y %m %d"] } return [clock scan "$today 00:00:00" -format "%Y %m %d %T"] } # Base-64 according to RFC 4648 # See https://wiki.tcl-lang.org/page/base64 if {![exists -command binary]} { package require binary } # RFC 4648 section 4 set ::b64::map { 000000 A 000001 B 000010 C 000011 D 000100 E 000101 F 000110 G 000111 H 001000 I 001001 J 001010 K 001011 L 001100 M 001101 N 001110 O 001111 P 010000 Q 010001 R 010010 S 010011 T 010100 U 010101 V 010110 W 010111 X 011000 Y 011001 Z 011010 a 011011 b 011100 c 011101 d 011110 e 011111 f 100000 g 100001 h 100010 i 100011 j 100100 k 100101 l 100110 m 100111 n 101000 o 101001 p 101010 q 101011 r 101100 s 101101 t 101110 u 101111 v 110000 w 110001 x 110010 y 110011 z 110100 0 110101 1 110110 2 110111 3 111000 4 111001 5 111010 6 111011 7 111100 8 111101 9 111110 + 111111 / } set ::b64::unmap [join [lmap {a b} $::b64::map {list $b $a}]] proc ::b64::encode {str} { binary scan $str B* bits switch [expr {[string length $bits]%6}] { 0 {set tail {}} 2 {append bits 0000; set tail ==} 4 {append bits 00; set tail =} } return [string cat [string map $::b64::map $bits] $tail] } proc ::b64::decode {str} { set nstr [string trimright $str =] set dstr [string map $::b64::unmap $nstr] switch [expr [string length $str]-[string length $nstr]] { 0 {#nothing to do} 1 {set dstr [string range $dstr 0 {end-2}]} 2 {set dstr [string range $dstr 0 {end-4}]} } return [binary format B* $dstr] } # RFC 4648 section 5 proc ::b64::url_encode {str} { tailcall string map {+ - / _ = ""} [::b64::encode $str] } proc ::b64::url_decode {str} { tailcall ::b64::decode [string map {- + _ /} $str] } alias b64encode ::b64::encode alias b64decode ::b64::decode alias b64uencode ::b64::url_encode alias b64udecode ::b64::url_decode # ECMA-262 Annex B.2.1 proc ::js::_escape {str} { return [join [lmap c [split $str ""] { if {1 != [scan $c "%c" cc]} { format "%s" $c } elseif {$cc < 256} { format "%%%02X" $cc } else { format "%%u%04X" $cc } }] ""] } proc ::js::_unescape {str} { if {1 == [scan $str "%%%2x" cc] || 1 == [scan $str "%%u%4x" cc]} { return [format "%c" $cc] } else { return $str } } proc ::js::escape {str} { return [subst -nobackslashes -novariables \ [regsub -all -- {[^A-Za-z0-9@*_+./-]+} $str \ {[::js::_escape {&}]}]] } proc ::js::unescape {str} { return [subst -nobackslashes -novariables \ [regsub -all -- {%(u[[:xdigit:]]{2})?[[:xdigit:]]{2}} $str \ {[::js::_unescape {&}]}]] } alias jsescape ::js::escape alias jsunescape ::js::unescape