var isupper = func(c) { c >= `A` and c <= `Z` } var islower = func(c) { c >= `a` and c <= `z` } var toupper = func(c) { islower(c) ? c + `A` - `a` : c } var tolower = func(c) { isupper(c) ? c + `a` - `A` : c } var isletter = func(c) { isupper(c) or islower(c) } var isdigit = func(c) { c >= `0` and c <= `9` } var isalnum = func(c) { isletter(c) or isdigit(c) } var isspace = func(c) { c == ` ` or c == `\t` or c == `\n` or c == `\r` } ## # trim spaces at the left (lr < 0), at the right (lr > 0), or both (lr = 0) # var trim = func(s, lr = 0) { var l = 0; if (lr <= 0) for (; l < size(s); l += 1) if (!isspace(s[l])) break; var r = size(s) - 1; if (lr >= 0) for (; r >= 0; r -= 1) if (!isspace(s[r])) break; return r < l ? "" : substr(s, l, r - l + 1); } ## # return string converted to lower case letters # var lc = func(str) { var s = ""; for (var i = 0; i < size(str); i += 1) s ~= chr(tolower(str[i])); return s; } ## # return string converted to upper case letters # var uc = func(str) { var s = ""; for (var i = 0; i < size(str); i += 1) s ~= chr(toupper(str[i])); return s; } ## # case insensitive string compare and match functions # (not very effective -- converting the array to be sorted # first is faster) # var icmp = func(a, b) cmp(lc(a), lc(b)); var imatch = func(a, b) match(lc(a), lc(b)); ## # check if string matches shell style pattern # # Rules: # ? stands for any single character # * stands for any number (including zero) of arbitrary characters # \ escapes the next character and makes it stand for itself; that is: # \? stands for a question mark (not the "any single character" placeholder) # [] stands for a group of characters: # [abc] stands for letters a, b or c # [^abc] stands for any character but a, b, and c # [1-4] stands for digits 1 to 4 (1, 2, 3, 4) # [1-4-] stands for digits 1 to 4, and the minus # [-1-4] same as above # [1-3-6] stands for digits 1 to 3, minus, and 6 # [1-3-6-9] stands for digits 1 to 3, minus, and 6 to 9 # [][] stands for the closing and the opening bracket (']' must be first!) # [^^] stands for all characters but the caret symbol # # Note that a minus can't be a range delimiter, as in [a--b] # # Example: # string.match(name, "*[0-9].xml"); ... true if 'name' ends with digit followed by ".xml" # var match = func(str, patt) { var s = 0; for (var p = 0; p < size(patt) and s < size(str); ) { if (patt[p] == `\\`) { if ((p += 1) >= size(patt)) return 0; # pattern ends with backslash } elsif (patt[p] == `?`) { s += 1; p += 1; continue; } elsif (patt[p] == `*`) { for (; p < size(patt); p += 1) if (patt[p] != `*`) break; if (p >= size(patt)) return 1; for (; s < size(str); s += 1) if (match(substr(str, s), substr(patt, p))) return 1; continue; } elsif (patt[p] == `[`) { setsize(var x = [], 256); var invert = 0; if ((p += 1) < size(patt) and patt[p] == `^`) { p += 1; invert = 1; } for (var i = 0; p < size(patt); p += 1) { if (patt[p] == `]` and i) break; x[patt[p]] = 1; i += 1; if (p + 2 < patt[p] and patt[p] != `-` and patt[p + 1] == `-` and patt[p + 2] != `]` and patt[p + 2] != `-`) { var from = patt[p]; var to = patt[p += 2]; for (var c = from; c <= to; c += 1) x[c] = 1; } } if (invert ? !!x[str[s]] : !x[str[s]]) return 0; s += 1; p += 1; continue; } if (str[s] != patt[p]) return 0; s += 1; p += 1; } return s == size(str) and p == size(patt); } ## # Removes superfluous slashes, empty and "." elements, expands # all ".." elements, and turns all backslashes into slashes. # The result will start with a slash if it started with a slash # or backslash, it will end without slash. Should be applied on # absolute property or file paths, otherwise ".." elements might # be resolved wrongly. # var fixpath = func(path) { var d = ""; for (var i = 0; i < size(path); i += 1) d ~= path[i] == `\\` ? "/" : chr(path[i]); var prefix = d[0] == `/` ? "/" : ""; var stack = []; foreach (var e; split("/", d)) { if (e == "." or e == "") continue; elsif (e == "..") pop(stack); else append(stack, e); } if (!size(stack)) return "/"; path = stack[0]; foreach (var s; subvec(stack, 1)) path ~= "/" ~ s; return prefix ~ path; }