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 .substr() {
arg @args;
return (> substr(@args) <);
};
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 = lowercase(strsed(str, "[^a-zA-Z0-9_]+", "", "g"));
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];
};