/
CDC-1.2b/
CDC-1.2b/src/
parent $libraries
object $string

var $root child_index 0
var $root owners [$string, $sys]
var $root fertile 0
var $root inited 1
var $string alphabet "abcdefghijklmnopqrstuvwxyz"
var $string numbers "1234567890"
var $string non_alphanumeric "!@#$%^&*()_+-=~`'{}[]|/?\"\\,.<>;: "
var $root owned [$string]
var $root manager $string
var $root writable [$string]
var $root readable ['parameters, 'methods, 'code]
var $root dbref 'string

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 + ((strlen(str) < width) ? pad("", width - strlen(str), fchar[1]) | "");
    else
        return str + ((strlen(str) < width) ? pad("", width - strlen(str)) | "");
.

method fill
    arg n, [args];
    var fill, x;
    
    // same as pad("", n, [args]);
    fill = [@args, " "][1];
    return pad("", n, fill);
.

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 = ((listlen(args) >= 1) && (args[1])) || " ";
    rfill = (listlen(args) >= 2) ? args[2] | ((lfill == " ") ? "" | lfill);
    textlen = strlen(text);
    padlen = (len - textlen) / 2;
    if (textlen < len)
        return ((.fill(padlen, lfill)) + text) + (rfill ? .fill(padlen, rfill) | "");
    else
        return (len > 0) ? text | pad(text, len);
.

method trim
    arg string, [args];
    var rl, chars, type;
    
    // remove leading and trailing characters.
    // if args includes a string, it takes that as the strip string.
    // args can also include symbols of what edge to trim: 'left 'right (or both)
    // left and right defaults on.
    rl = [];
    while (args) {
        type = type(args[1]);
        if (type == 'string)
            chars = args[1];
        else if (type == 'symbol)
            rl = [@rl, args[1]];
        args = sublist(args, 2);
    }
    if (!chars)
        chars = " ";
    if (!rl)
        rl = ['left, 'right];
    if ('left in rl) {
        // strip from left
        while (string && ((string[1]) in chars))
            string = substr(string, 2);
    }
    if ('right in rl) {
        // strip from right
        while (string && ((string[strlen(string)]) in chars))
            string = substr(string, 1, strlen(string) - 1);
    }
    return string;
.

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 = match_pattern(sep, str);
        if (result) {
            list = list + [result[1]];
            str = result[2];
        } else {
            return list + [str];
        }
    }
.

method right
    arg str, width, [fchar];
    
    // will not chop off 'str' if it is longer than width (unlike pad())
    if (fchar)
        return pad("", width - strlen(str), fill_char[1]) + str;
    else
        return pad("", width - strlen(str)) + str;
.

method alphabet
    return alphabet;
.

method numbers
    return numbers;
.

method capitalize
    arg string;
    
    // Capitalizes the first character of a word.
    return uppercase(string[1]) + substr(string, 2);
.

method is_numeric
    arg string;
    
    return toint(string) || (string == "0");
.

method a_or_an
    arg string;
    
    if (lowercase(string[1]) in "aeiou")
        return "an";
    return "a";
.

method strip
    arg string, strip;
    var new_str, char;
    
    // strips all of "strip" characters from the string
    // if "strip" is -1 it will use .non_alphanumeric()
    if ((type(string) != 'string) || ((type(strip) != 'string) && (strip != (-1))))
        throw(~type, "First argument must be a string, second can be -1");
    new_str = "";
    if (strip == (-1))
        new_str = non_alphanumeric;
    for char in [1 .. strlen(string)] {
        if (!((string[char]) in strip))
            new_str = new_str + (string[char]);
    }
    return new_str;
.

method non_alphanumeric
    return non_alphanumeric;
.

method chop
    arg str, len, [end];
    
    // chops string off strlen(end) characters before len and appends len
    end = [@end, "..."][1];
    if (strlen(str) < len)
        return str;
    if (strlen(str) < strlen(end))
        return str;
    return pad(str, len - strlen(end)) + end;
.

method replace
    arg string, replace, [char];
    var new_str, character;
    
    // replaces 'replace' with the character in 'char'
    // if 'replace' is -1 will use .non_alphanumeric();
    if ((type(string) != 'string) || ((type(replace) != 'string) && (strip != (-1))))
        throw(~type, "First argument must be a string, second can be -1");
    char = [@char, " "][1];
    new_str = "";
    if (replace == (-1))
        new_str = non_alphanumeric;
    for character in [1 .. strlen(string)] {
        if (!((string[character]) in replace))
            new_str = new_str + (string[character]);
        else
            new_str = new_str + char;
    }
    return new_str;
.

method explode_english_list
    arg line, [opts];
    var x, output, tmp;
    
    // explodes an english list ("foo, bar and zoo").
    line = explode(line, ",");
    output = [];
    for x in (line) {
        x = .trim(x);
        if ((| substr(x, 1, 3) |) == "and")
            output = [@output, .trim(substr(x, 4))];
        else
            output = [@output, x];
    }
    
    // check the last element, if they didn't specify  'noand
    if (!('noand in opts)) {
        line = explode(output[listlen(output)]);
        tmp = "";
        for x in [1 .. listlen(line)] {
            if ((line[x]) == "and") {
                output = delete(output, listlen(output));
                if (tmp)
                    output = [@output, tmp];
                tmp = $list.to_string(sublist(line, x + 1));
                if (tmp)
                    output = [@output, tmp];
    
                // only bother with the first "and"
                break;
            }
            tmp = (tmp + (tmp ? " " | "")) + (line[x]);
        }
    }
    return output;
.

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 = match_pattern(pattern, str);
        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];
.

method wrap_line
    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 = [];
    length = length - strlen(prefix);
    if (firstline)
        string = prefix + string;
    while (strlen(string) > length) {
        cutoff = .rindex(substr(string, 1, length), " ");
        output = [@output, substr(string, 1, cutoff - 1)];
        string = prefix + substr(string, cutoff + 1);
    }
    return [@output, string];
.

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 && substr(string, loc + 1);
    while (loc && (index in rest)) {
        loc = loc + (index in rest);
        rest = loc && substr(string, loc + 1);
    }
    return loc;
.

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.namef())) + " ";
            else
                output = ((output + tag) + (matches[x])) + " ";
        } else {
            output = output + x;
        }
    }
    return substr(output, 1, strlen(output) - 1);
.

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)(substr(text[line], line));
        if (match_result != 0) {
            if (type == 'match_pattern) {
                pat = $string.pat_sub(pat, match_result);
                return [line, (start_at + pat) in substr(text[line], start_at)];
            } else {
                return [line, start_at + ((match_result[1])[1])];
            }
        }
        line = line + 1;
    }
    while (line <= listlen(text)) {
        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.");
.

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]) + substr(pat, 2);
        else if (wc_idx == strlen(pat))
            pat = substr(pat, 1, wc_idx - 1) + (subs[1]);
        else
            pat = (substr(pat, 1, wc_idx - 1) + (subs[1])) + substr(pat, wc_idx + 1);
        subs = delete(subs, 1);
    }
    return pat;
.

method is_boolean
    arg str;
    
    if (match_begin("yes", str) || (match_begin("true", str) || (str == "1")))
        return 1;
    else if (match_begin("no", str) || (match_begin("false", str) || (str == "0")))
        return 0;
    else
        return -1;
.

method parse_template
    arg str;
    var index, out;
    
    out = explode(str, " *")[1];
    
    // index = "?" in str;
    // if (index) {
    //     out = uppercase(substr(str, 1, index - 1));
    //     out = out + "?" + substr(str, index + 1);
    // } else {
    //     out = uppercase(out);
    // }
    return out;
.

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;
.

method explode
    arg [args];
    
    return (> explode(@args) <);
.

method match_template
    arg [args];
    
    return (> match_template(@args) <);
.

method find_next
    arg str, choices;
    var t, first, pos;
    
    //Returns the index of the first string in choices to appear.
    //Returns strlen(str) if none are in str.
    first = strlen(str) + 1;
    for t in (choices) {
        pos = t in str;
        if (pos && (pos < first))
            first = pos;
    }
    return first;
.

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 = (| substr(str, 1, pos - 1) |) || "";
    post = (| substr(str, pos + 1) |) || "";
    return [pre, (| str[pos] |) || "", post];
.

method explode_template_word
    arg template;
    var t, x, idx, out, new;
    
    // this only explodes single word templates
    template = explode(template, "|");
    out = [];
    for t in (template) {
        idx = "?" in t;
        if (idx) {
            t = t.strip("?");
            new = substr(t, 1, idx - 1);
            out = [@out, new];
            for x in [idx .. strlen(t)] {
                new = new + (t[x]);
                out = [@out, new];
            }
        } else {
            out = [@out, t];
        }
    }
    return out;
.

method match_pattern
    arg [args];
    
    return (> match_pattern(@args) <);
.

method match_regexp
    arg [args];
    
    return (> match_regexp(@args) <);
.

method to_number
    arg str;
    
    if (str.is_numeric())
        return toint(str);
    throw(~type, ("\"" + str) + "\" is not a number.");
.