new object $string: $libraries; var $root inited = 1; var $string alphabet = "abcdefghijklmnopqrstuvwxyz"; var $string non_alphanumeric = "!@#$%^&*()_+-=~`'{}[]|/?\",.<>;: "; var $string numbers = "1234567890"; public method .a_or_an() { arg string; if (string[1].lowercase() in "aeiou") return "an"; return "a"; }; public method .alphabet() { return alphabet; }; public method .regexp() { arg string, regexp; var match, i, out; out = []; match = match_regexp(string, regexp); if (!match) { return []; } for i in (sublist(match, 2)) { if (i[1] > 0) { out += [substr(string, @i)]; } else { out += [""]; } } return out; }; public method .center() { arg text, len, [args]; var lfill, rfill, textlen, padlen; // args[1] == string to center // args[2] == integer of width to center in // args[3] <op> == what to fill the left|right side with. // args[4] <op> == what to fill the right side with. lfill = args.length() >= 1 && args[1] || " "; rfill = args.length() >= 2 ? args[2] : lfill == " " ? "" : lfill; textlen = text.length(); padlen = (len - textlen) / 2; if (textlen < len) return .fill(padlen, lfill) + text + (rfill ? .fill(len - textlen - padlen, rfill) : ""); else return len > 0 ? text : text.pad(len); }; public method .chop() { arg str, len, [end]; // chops string off end.length() characters before len and appends len end = [@end, "..."][1]; if (str.length() < len) return str; if (str.length() < end.length()) return str; return str.pad(len - end.length()) + end; }; public method .echo() { arg str; return str; }; public method .explode_delimited() { arg str, left, right; var pattern, parsed, matched, match_num, match_result; // parse str looking for anything surrounded by left and right // ;$string.explode_delimited("foo%[bar]baz", "%[", "]") // => [["foo", 1, "baz"], ["bar"]] pattern = "*" + left + "*" + right + "*"; parsed = []; matched = []; match_num = 0; while (str) { match_result = str.match_pattern(pattern); if (match_result) { match_num = match_num + 1; parsed = [@parsed, match_result[1], match_num]; matched = [@matched, match_result[2]]; str = match_result[3]; } else { parsed = [@parsed, str]; str = ""; } } return [parsed, matched]; }; public method .explode_english_list() { arg line, [opts]; var x, output, tmp; if (!line) return []; // explodes an english list ("foo, bar and zoo"). line = line.explode(","); output = []; for x in (line) { x = .trim(x); if ((| x.subrange(1, 4) |) == "and ") output = [@output, .trim(x.subrange(4))]; else output = [@output, x]; } // check the last element, if they didn't specify 'noand if (!('noand in opts)) { line = output[output.length()].explode(); tmp = ""; for x in [1 .. line.length()] { if (line[x] == "and") { output = output.delete(output.length()); if (tmp) output = [@output, tmp]; tmp = $list.join(line.subrange(x + 1)); if (tmp) output = [@output, tmp]; // only bother with the first "and" break; } tmp = tmp + (tmp ? " " : "") + line[x]; } } return output; }; public method .explode_list() { arg str; if ("," in str) return str.explode_english_list(); else return str.explode(); }; public method .explode_quoted() { arg str; var out, result; out = []; while (str) { result = match_pattern(str, "*\"*\"*"); if (result) { out = [@out, @result[1].explode(), result[2].trim()]; str = result[3]; } else { out = [@out, @str.explode()]; str = ""; } } return out; }; public method .explode_template_word() { arg template; var t, x, idx, out, new; // this only explodes single word templates template = template.explode("|"); out = []; for t in (template) { idx = "?" in t; if (idx) { t = t.strip("?"); new = t.subrange(1, idx - 1); out = [@out, new]; for x in [idx .. t.length()] { new = new + t[x]; out = [@out, new]; } } else { out = [@out, t]; } } return out; }; public method .fill() { arg n, [args]; var fill, x; // same as pad("", n, [args]); fill = [@args, " "][1]; return "".pad(n, fill); }; public method .find_escaped() { arg str, char; var good, start, pos, p; good = 0; start = 0; while (!good && start < str.length()) { pos = (char in str.subrange(start + 1)) + start; good = 1; if (pos > start) { p = pos - 1; while (p > 0 && str[p] == "\\") { good = good ? 0 : 1; p = p - 1; } } if (good) return pos; else start = pos; } }; public method .find_next() { arg str, choices; var t, first, pos; //Returns the index of the first string in choices to appear. //Returns str.length() if none are in str. first = str.length() + 1; for t in (choices) { pos = t in str; if (pos && pos < first) first = pos; } return first; }; public method .find_next_escaped() { arg str, choices; var t, first, pos, good, p, start; //Returns the index of the first string in choices to appear. //If //Returns str.length() if none are in str. first = str.length() + 1; for t in (choices) { pos = str.find_escaped(t); if (pos < first) first = pos; } return first; }; public method .is_boolean() { arg str; if ("yes".match_begin(str) || "true".match_begin(str) || str == "1") return 1; else if ("no".match_begin(str) || "false".match_begin(str) || str == "0") return 0; return -1; }; public method .is_numeric() { arg string; return toint(string) || string == "0"; }; public method .last() { arg str; return str[str.length()]; }; public method .left() { arg str, width, [fchar]; // will NOT chop off 'str' if it is longer than width, use pad() for that. if (fchar) return str + (str.length() < width ? "".pad(width - str.length(), fchar[1]) : ""); else return str + (str.length() < width ? "".pad(width - str.length()) : ""); }; public method .match_sub_tag() { arg string, tag; var x, expl, output, match, matches; // matches a string between 'tag' and " " in a larger string against // the sender's environment. If a match is found it subs the match.name // with the string, otherwize it lets it pass through with the tag, ie: // .match_sub_tag("this test #of something #note or other"); // => "this test #of something Note of sorts or other" // where the note is in the sender's environment. expl = .explode_delimited(string + " ", tag, " "); matches = expl[2]; expl = expl[1]; output = ""; for x in (expl) { if (type(x) == 'integer) { match = (| sender().match_environment(matches[x]) |); if (match) output = output + match.name() + " "; else output = output + tag + matches[x] + " "; } else { output = output + x; } } return output.subrange(1, output.length() - 1); }; public method .non_alphanumeric() { return non_alphanumeric; }; public method .numbers() { return numbers; }; public method .onespace() { arg str; var sub; if ((sub = " +".sed(str, " ", "g"))) str = sub; return str; }; public method .parse_template() { arg str; var index, out; out = (str.explode(" *"))[1]; // index = "?" in str; // if (index) { // out = uppercase(str.subrange(1, index - 1)); // out = out + "?" + str.subrange(index + 1); // } else { // out = uppercase(out); // } return out; }; public method .pat_sub() { arg pat, subs; var wc_idx; // wc_idx == wildcard index while (subs) { wc_idx = "*" in pat; if (wc_idx == 1) pat = subs[1] + pat.subrange(2); else if (wc_idx == pat.length()) pat = pat.subrange(1, wc_idx - 1) + subs[1]; else pat = pat.subrange(1, wc_idx - 1) + subs[1] + pat.subrange(wc_idx + 1); subs = subs.delete(1); } return pat; }; public method .repeat() { arg string, times; var t, out; // repeats <string> <times> times if (type(string) != 'string) throw(~type, "The first agrument must be a string."); if (type(times) != 'integer || times < 0) throw(~type, "The second agrument must be a non-negatiive integer."); out = ""; for t in [1 .. times] out = out + string; return out; }; public method .right() { arg str, width, [fchar]; // will not chop off 'str' if it is longer than width (unlike pad()) if (fchar) return "".pad(width - str.length(), fchar[1]) + str; else return "".pad(width - str.length()) + str; }; public method .rindex() { arg string, index; var loc, rest; // returns the first occurance of index starting from the end of the string, // and moving to the beginning. loc = index in string; rest = loc && string.subrange(loc + 1); while (loc && index in rest) { loc = loc + (index in rest); rest = loc && string.subrange(loc + 1); } return loc; }; public method .rindexc() { arg str, c; var i; // same as rindex, but only with a single character, faster. i = str.length(); while (i) { if (str[i] == c) return i; i = i - 1; } return 0; }; public method .search_pat() { arg pat, text, [start_at]; var line, match_result, type; line = 1; type = [@start_at, 'pattern, 'pattern][2] == 'pattern ? 'match_pattern : 'match_regexp; if (start_at) { line = start_at[1]; start_at = [@start_at, 1, 1][2]; match_result = pat.(type)(text[line].subrange(line)); if (match_result != 0) { if (type == 'match_pattern) { pat = $string.pat_sub(pat, match_result); return [line, start_at + pat in text[line].subrange(start_at)]; } else { return [line, start_at + match_result[1][1]]; } } line = line + 1; } while (line <= text.length()) { match_result = pat.(type)(text[line]); if (match_result != 0) { if (type == 'pattern) { pat = $string.pat_sub(pat, match_result); return [line, pat in text[line]]; } else { return [line, match_result[1][1]]; } } line = line + 1; } throw(~strnf, "String not found in text."); }; public method .split_on_next() { arg str, choices; var pos, pre, post; // splits str around whichever choice appears first. pos = $string.find_next(str, choices); pre = (| str.subrange(1, pos - 1) |) || ""; post = (| str.subrange(pos + 1) |) || ""; return [pre, (| str[pos] |) || "", post]; }; public method .split_on_next_escaped() { arg str, choices; var pos, pre, post; // splits str around whichever choice appears first. pos = str.find_next_escaped(choices); pre = (| str.subrange(1, pos - 1) |) || ""; post = (| str.subrange(pos + 1) |) || ""; return [pre, (| str[pos] |) || "", post]; }; public method .strip() { arg string, [strip]; var new_str, char; new_str = ""; if (!strip) strip = "!@#$%^&*()_+-=~`'{}[]|/?\"\,.<>;: "; else strip = strip[1]; for char in [1 .. strlen(string)] { if (!(string[char] in strip)) new_str = new_str + string[char]; } return new_str; }; public method .strip_article() { arg str; return strsed(str, "^(an|a|the) *", "", "g"); }; public method .strip_others() { arg string, valid; var new_str, char; // strips all but "strip" characters from the string new_str = ""; for char in [1 .. string.length()] { if (string[char] in valid) new_str = new_str + string[char]; } return new_str; }; public method .sub() { arg regexp, string; var match, m, complete, out; match = string.match_regexp(regexp); if (!match) return 0; complete = match[1]; out = []; for m in (match.delete(1)) { if (!m[1]) break; out = out + [string.subrange(@m)]; } return out || string.subrange(@complete); }; public method .to_buffer() { arg string; return (> $buffer.from_string(string) <); }; public method .to_list() { arg str, [sep]; var result, list; // separate a string into a list of strings, breaking wherever 'sep' appears. // if not provided, sep defaults to a comma. // One word of warning. sep should not contain an asterisk. If it does, // this routine will separate the string oddly, most likely losing bits. if (!str) return []; sep = "*" + (sep ? sep[1] : ",") + "*"; list = []; while (1) { result = str.match_pattern(sep); if (result) { list = list + [result[1]]; str = result[2]; } else { return list + [str]; } } }; public method .to_number() { arg str; if (str.is_numeric()) return toint(str); throw(~nonum, "\"" + str + "\" is not a number."); }; public method .to_symbol() { arg str; str = str.strip(); return (> tosym(str) <); }; public method .toliteral() { arg [args]; return (> toliteral(@args) <); }; public method .unquote() { arg str; if (str && str[1] == "\"" && str[str.length()] == "\"") return str.subrange(2, str.length() - 2); return str; }; public method .valid_ident() { arg str; return (| tosym(str) |) || 0; }; public method .valid_method_name() { arg str; return .strip_others(str, alphabet + numbers + "_").length() == str.length(); }; public method .wrap_line() { arg string, length, [stuff]; var output, cutoff, firstline, prefix; // takes string and wraps it by words, compared to length, breaks with \n prefix = [@stuff, ""][1]; firstline = [@stuff, 0, 0][2]; output = ""; if (firstline) string = prefix + string; while (string.length() > length) { cutoff = .rindex(string.subrange(1, length), " "); if (cutoff <= prefix.length()) { output += "\n" + string.subrange(1, length); string = prefix + string.subrange(length + 1); } else { output += "\n" + string.subrange(1, cutoff - 1); string = prefix + string.subrange(cutoff + 1); } } return (output ? output.subrange(3) + "\n" : "") + string; }; public method .wrap_lines() { arg string, length, [stuff]; var output, cutoff, firstline, prefix; // takes string and wraps it by words, compared to length, returns a list. prefix = [@stuff, ""][1]; firstline = [@stuff, 0, 0][2]; output = []; if (firstline) string = prefix + string; while (string.length() > length) { cutoff = .rindex(string.subrange(1, length), " "); if (cutoff <= prefix.length()) { output = [@output, string.subrange(1, length)]; string = prefix + string.subrange(length + 1); } else { output = [@output, string.subrange(1, cutoff - 1)]; string = prefix + string.subrange(cutoff + 1); } } return [@output, string]; };