First, create some new $string_utils verbs and modify explode(). Be careful with your line wrapping. --------------------------------------------------------------------------------- @verb $string_utils:"dirname" this none this xd @program $string_utils:"dirname" "strip non-directory suffix from file name"; bits = $string_utils:explode(args[1], "/"); return tostr("/", $string_utils:from_list(bits[1..$ - 1], "/")); . @verb $string_utils:"basename" this none this xd @program $string_utils:"basename" "strip non-directory suffix from file name"; bits = $string_utils:explode(args[1], "/"); if (bits) return bits[$]; else return "/"; endif . @verb $string_utils:"normalize_path" this none this xd @program $string_utils:"normalize_path" return "/" + $string_utils:from_list($string_utils:explode(args[1], "/"), "/"); . @program $string_utils:"explode" "$string_utils:explode(subject [, break])"; "Return a list of those substrings of subject separated by runs of break[1]."; "break defaults to space."; "supply an optional true 3rd argument to return empty list elements"; {subject, ?breakit = {" "}, ?empty = 0} = args; breakit = breakit[1]; subject = subject + breakit; parts = {}; if (empty) empty = 0; else empty = 1; endif while (subject) if ((i = index(subject, breakit)) > empty) parts = {@parts, subject[1..i - 1]}; endif subject = subject[i + 1..$]; endwhile return parts; . @verb $prog:"cvs" any any any x @program $prog:"cvs" $cvs:_cvs(argstr); . @prop $root_class."cvs_data" {} "" @create $root_class named CVS Client Object:CVS Client Object --------------------------------------------------------------------------------- Edit and run the next line to set #0.cvs to the object number that you just created: @prop #0."cvs" #-1 The remainder of the listing is the actual CVS code: --------------------------------------------------------------------------------- @prop $cvs."open_connections" {} "" @prop $cvs."valid_responses" {} "" ;$cvs.("valid_responses") = {"ok", "error", "Valid-requests", "Checked-in", "New-entry", "Copy-file", "Updated", "Created", "Update-existing", "Merged", "Patched", "Removed", "Remove-entry", "Set-static-directory", "Clear-static-directory", "Set-sticky", "Clear-sticky", "Template", "Set-checkin-prog", "Set-update-prog", "Notified", "Module-expansion", "Wrapper-rcsOption", "Rcs-diff", "M", "E", "F"} @prop $cvs."timeout" 60 "" @prop $cvs."debug" 0 "" @prop $cvs."commands" {} r ;$cvs.("commands") = {"add", "annotate", "co", "ci", "diff", "log", "login", "logout", "rdiff", "remove", "rtag", "status", "tag", "update"} @prop $cvs."command_short_help" {} r ;$cvs.("command_short_help") = {"Add a new file/directory to the repository", "Show last revision where each line was modified", "Checkout sources for editing", "Check files into the repository", "Show differences between revisions", "Print out history information for files", "Prompt for password for authenticating server", "Removes entry in .cvspass for remote repository", "Create 'patch' format diffs between releases", "Remove an entry from the repository", "Add a symbolic tag to a module", "Display status information on checked out files", "Add a symbolic tag to checked out version of files", "Bring work tree in sync with repository"} @prop $cvs."command_opts" {} r ;$cvs.("command_opts") = {"d:m:", "fr:D:", "Acfnpsr:D:d:j:", "nfF:m:r:", "bBcC:dfHinsuU:wD:Nr:", "RhtNbr::d:s:w::", "", "", "fcustD:r:", "f", "afndbr:D:F", "v", "dr:D:fbFc", "ACdfpr:D:j:"} @prop $cvs."command_aliases" {} r ;$cvs.("command_aliases") = {{"add"}, {"annotate", "ann*otate", "blame"}, {"checkout"}, {"commit", "checkin"}, {"diff"}, {"log"}, {"login"}, {"logout"}, {"rdiff"}, {"remove", "rm"}, {"rtag"}, {"status"}, {"tag"}, {"update", "up*date"}} @prop $cvs."version" "0.1" r ;$cvs.("aliases") = {"CVS Client Object"} @verb $cvs:"write" this none this xd @program $cvs:write "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; data = args[2..$]; if (!$network:is_open(conn)) return E_INVARG; endif line = encode_binary(tostr(@data)); set_connection_option(conn, "binary", 1); notify(conn, tostr(line, "~0A")); if (this.debug) player:tell_noansi("out: ", line); endif set_connection_option(conn, "binary", 0); . @verb $cvs:"open_connection" none none none xd @program $cvs:open_connection "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; cvsroot = args[1]; if (length(args) > 1) passwd = this:crypt_passwd(args[2]); else cvspass = $list_utils:assoc(cvsroot, player.cvspass); if (cvspass) passwd = cvspass[2]; else "try empty string just for kicks"; passwd = "A"; endif endif parsed = this:parse_cvsroot(cvsroot); if (typeof(parsed) == ERR) player:tell("cvs: Bad CVSROOT."); return parsed; endif {server, root, user} = parsed; c = this:start_connection(server); this:write(c, "BEGIN AUTH REQUEST"); this:write(c, root); this:write(c, user); this:write(c, passwd); this:write(c, "END AUTH REQUEST"); result = this:read(c); if (result != "I LOVE YOU") return E_PERM; endif this:complete_handshake(c, root); this.open_connections = {@this.open_connections, c}; return c; . @verb $cvs:"read" this none this xd @program $cvs:read "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; line = this:tcp_wait(conn, this.timeout); if (this.debug) player:tell_noansi("in :", line); endif return line; . @verb $cvs:"tcp_wait" this none this xd #2 @program $cvs:tcp_wait "Copied from Network Utilities (#72):tcp_wait by phil (#95) Tue Nov 27 18:58:04 2001 EST"; "The $network version looks for an SMTP return code at the front of the string,"; "so we have this (sigh)"; {conn, ?timeout = 0} = args; parent = task_id(); if (!caller_perms().wizard) return E_PERM; elseif (timeout) fork task (timeout) boot_player(conn); player:tell(">>> CVS socket timeout"); `kill_task(parent) ! ANY'; endfork endif line = `read(conn) ! ANY'; if (timeout) `kill_task(task) ! ANY'; endif return line; . @verb $cvs:"crypt_passwd" this none this xd @program $cvs:crypt_passwd "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; ""; "Whatever moron thought that this ``encryption'' system was a good idea is"; "going straight to hell."; old = {"!", "\"", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "_", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}; new = {120, 53, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, 111, 52, 75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, 57, 83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, 125, 55, 54, 66, 124, 126, 59, 47, 92, 71, 115, 56, 121, 117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, 58, 113, 32, 90, 44, 98, 60, 51, 33, 97, 62}; passwd = args[1]; "passwords always start with 'A'"; dorky = {65}; for c in ($string_utils:char_list(passwd)) i = is_member(c, old); if (i == 0) "this is dorky, but what else to do?"; dorky = {@dorky, @decode_binary(c, 1)}; else dorky = {@dorky, new[i]}; endif endfor return encode_binary(@dorky); . @verb $cvs:"parse_cvsroot" this none this xd @program $cvs:parse_cvsroot cvsroot = this:normalize_cvsroot(args[1]); return this:internal_parse_cvsroot(cvsroot); . @verb $cvs:"_login" this none this xd @program $cvs:_login "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; cvsroot = this:normalize_cvsroot(args[1]); {server, root, user} = this:parse_cvsroot(cvsroot); conn = this:start_connection(server); if (typeof(conn) == ERR) player:tell("Unable to establish connection."); return; endif passwd = $command_utils:read("your CVS password"); cpasswd = this:crypt_passwd(passwd); this:write(conn, "BEGIN VERIFICATION REQUEST"); this:write(conn, root); this:write(conn, user); this:write(conn, cpasswd); this:write(conn, "END VERIFICATION REQUEST"); result = this:read(conn); if (result != "I LOVE YOU") player:tell("cvs login: authorization failed: server ", server, " rejected access to ", root, " for user ", user); this:close(conn); else player:tell("cvs login: success"); cp = $list_utils:iassoc(cvsroot, player.cvspass); if (cp) player.cvspass[cp][2] = cpasswd; else player.cvspass = {@player.cvspass, {cvsroot, cpasswd}}; endif endif . @verb $cvs:"complete_handshake" this none this xd @program $cvs:complete_handshake "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; c = args[1]; root = args[2]; this:write(c, "Root ", root); this:write(c, "Valid-responses ", $string_utils:from_list(this.valid_responses, " ")); this:write(c, "valid-requests"); lines = this:read_response(c); this:write(c, "UseUnchanged"); this:write(c, "update-patches"); . @verb $cvs:"close" this none this xd @program $cvs:close conn = args[1]; this.open_connections = setremove(this.open_connections, conn); $network:close(conn); return 0; . @verb $cvs:"_co" this none this xd @program $cvs:_co "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; ""; "This function is unfinished. Do not be surprised if it misbehaves."; conn = args[1]; cvsroot = args[2]; params = args[3]; items = args[4]; if (!items) if (({"c", ""} in params) || ({"s", ""} in params)) for p in (params) this:write(conn, "Argument -", p[1]); endfor else return E_INVARG; endif else for item in (items) $command_utils:suspend_if_needed(0); {type, object, name} = this:parse_objstr(item, 1); {server, root, user} = this:parse_cvsroot(cvsroot); if (type == "error") objname = ""; for p in (params) if (!strcmp(p[1], "d")) objname = p[2]; endif endfor if ((!objname) || $command_utils:object_match_failed(object = player:my_match_object(objname), objname)) player:tell("cvs [checkout aborted]: You must specify an object to checkout into with `-d'"); return E_INVARG; endif type = "object"; endif "Treat this like an object update, which is basically what it is"; if (!object.cvs_data) player:tell("cvs [checkout aborted]: there is no version here; use `cvs checkout' to create a new object or use `cvs add' to create an entry"); return; endif repository = root + object.cvs_data[2][2]; for p in (params) if (strcmp(p[1], "d") != 0) this:write(conn, "Argument -", p[1], p[2]); endif endfor this:write(conn, "Argument ", object.cvs_data[2][2][2..$]); this:write(conn, "Directory ", object.cvs_data[2][2][2..$]); this:write(conn, repository); retval = this:send_entries_data(conn, repository, type, object, name); if (typeof(retval) != ERR) this:write(conn, "co"); l = this:read_response(conn); resp = this:parse_response(@l); resp = this:handle_common_responses(object, resp); for r in (resp) if ((length(r) == 1) && ($string_utils:explode(r[1], " ")[1] == "error")) break; else player:tell("Unexpected response in $cvs:", verb, ": tell phil (", toliteral(r), ")"); break; endif endfor endif endfor endif . @verb $cvs:"normalize_cvsroot" this none this xd @program $cvs:normalize_cvsroot "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; cvsroot = args[1]; {server, root, user} = this:internal_parse_cvsroot(cvsroot); "we force pserver on people; tough shit. this is ugly. oh well."; return tostr(":pserver:", user, "@", server, ":/", $string_utils:from_list($string_utils:explode(root, "/"), "/")); . @verb $cvs:"start_connection" this none this xd @program $cvs:start_connection "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; server = args[1]; c = `open_network_connection(server, 2401) ! ANY'; if (typeof(c) != ERR) set_connection_option(c, "hold-input", 1); set_connection_option(c, "flush-command", ""); endif return c; . @verb $cvs:"read_response" this none this xd @program $cvs:read_response "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; lines = {}; while (1) result = this:read(conn); if (typeof(result) == ERR) this:close(conn); break; endif "M, E, and F are easy to handle right now, so Just Do It"; command = $string_utils:explode(result, " ")[1]; if ((command == "E") || (command == "M")) player:tell_noansi(result[3..$]); continue; endif if (command == "F") "F is 'flush output' which we always do right away anyways"; continue; endif lines = {@lines, result}; result = $string_utils:trimr(result); if ((result == "ok") || (result == "error")) break; endif endwhile return lines; . @verb $cvs:"_add" this none this xd @program $cvs:_add "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; cvsroot = this:normalize_cvsroot(args[2]); params = args[3]; items = args[4]; directory = logmsg = ""; for p in (params) if (p[1] == "m") logmsg = p[2]; elseif (p[1] == "d") directory = p[2]; endif endfor for item in (items) $command_utils:suspend_if_needed(0); {type, object, name} = this:parse_objstr(item); if (type == "error") this:_add_usage(); return E_INVARG; endif if (type == "object") if (object.cvs_data) player:tell("cvs [add aborted]: there is a version of \"", object.name, "\" (", object, ") in CVS already"); return; elseif (!directory) player:tell("You must specify a directory in which to add this object."); this:_add_usage(); return; endif elseif (!object.cvs_data) player:tell("cvs [add aborted]: nothing known about \"", object.name, "\" (", object, "); you must add the object before you can add properties or verbs."); return; endif {server, root, user} = this:parse_cvsroot(cvsroot); if (logmsg) this:write(conn, "Argument -m"); this:write(conn, "Argument ", logmsg); endif error = 0; if (type == "object") name = $string_utils:normalize_path((((root + "/") + directory) + "/") + object.name); for path in ({name, (name + "/") + "verbs", (name + "/") + "props"}) error = this:_add_dir(conn, path); if (error) break; endif endfor if (!error) repo = $string_utils:normalize_path((directory + "/") + object.name); object.cvs_data = {{"Root", cvsroot}, {"Repository", repo}, {"Entries", {}}}; endif else if (type == "verb") name = "/verbs/" + name; else name = "/props/" + name; endif e = this:get_entry(object, name); if (length(e) > 6) "remove the /verbs/ or /props/ from the front"; e = e[7..$]; endif path = (root + object.cvs_data[2][2]) + name; entries = this:_add_file(conn, object, path, e); if (typeof(entries) != ERR) this:add_entry(object, name + "/0///"); endif endif endfor . @verb $cvs:"internal_parse_cvsroot" this none this xd @program $cvs:internal_parse_cvsroot "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; cvsroot = args[1]; first = $string_utils:explode(cvsroot, ":"); if (length(first) != 3) return E_INVARG; endif second = $string_utils:explode(first[2], "@"); if (length(second) != 2) return E_INVARG; endif "{server, root, user}"; return {second[2], first[3], second[1]}; . @verb $cvs:"parse_args" this none this xd @program $cvs:parse_args "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; ""; "acts something like getopt(3), at least in theory"; "adding two spaces now saves a lot of length() checking later"; optstr = tostr(args[1], " "); args = $string_utils:explode(args[2], " "); if (!args) return {{}, "", {}}; endif opts = {}; newargs = ""; command = ""; next_argument = "no"; arg_waiting = ""; for ai in [1..length(args)] a = args[ai]; if (next_argument == "required") if (a[1] == "-") player:tell("option requires an argument -- ", arg_waiting); return E_INVARG; endif endif if ((next_argument != "no") && (a[1] != "-")) opts = {@opts, {arg_waiting, a}}; next_argument = "no"; continue; endif if (((a[1] == "-") && (a != "-")) && (a != "--")) if (length(a) > 1) for n in [2..length(a)] i = index(optstr, a[n]); if (i == 0) opts = {@opts, {"?", a[n]}}; continue; endif if (optstr[i + 1] == ":") if (length(a) > n) "the rest of this -fblahblah is the argument"; opts = {@opts, {a[n], a[n + 1..$]}}; break; endif if (optstr[i + 2] == ":") next_argument = "optional"; else next_argument = "required"; endif arg_waiting = a[n]; else opts = {@opts, {a[n]}}; endif endfor endif else "after the first non-argument item (the 'command'), stop parsing"; command = a; newargs = $string_utils:from_list(args[ai + 1..$], " "); break; endif endfor return {opts, command, newargs}; . @verb $cvs:"_add_usage" this none this xd @program $cvs:_add_usage player:tell("Usage: cvs add [-d directory] [-m message] objects|properties|verbs..."); player:tell(" -d Add this object to \"directory\"; mandatory for objects, ignored"); player:tell(" for properties and verbs."); player:tell(" -m Use \"message\" for the creation log."); player:tell("(Specify the -h global option for a list of other help options)"); . @verb $cvs:"_add_dir" this none this xd @program $cvs:_add_dir "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; path = args[2]; this:write(conn, "Directory ", $string_utils:basename(path)); this:write(conn, path); this:write(conn, "Directory ."); this:write(conn, $string_utils:dirname(path)); this:write(conn, "Argument ", $string_utils:basename(path)); this:write(conn, "add"); l = this:read_response(conn); return this:parse_response(@l); . @verb $cvs:"_add_file" this none this xd @program $cvs:_add_file "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; object = args[2]; path = args[3]; if (length(args) > 3) entry = args[4]; else entry = ""; endif this:write(conn, "Directory ."); this:write(conn, $string_utils:dirname(path)); if (entry) this:write(conn, "Entry ", entry); endif "Only write an Is-modified line if the file exists"; dir = $string_utils:basename($string_utils:dirname(path)); name = $string_utils:basename(path); if ((dir == "verbs") && $object_utils:has_verb(object, name)) this:write(conn, "Is-modified ", name); elseif ((dir == "props") && $object_utils:hash_property(object, name)) this:write(conn, "Is-modified ", name); endif this:write(conn, "Argument ", name); this:write(conn, "add"); l = this:read_response(conn); resp = this:parse_response(@l); resp = this:handle_common_responses(object, resp); for r in (resp) if ((length(r) == 1) && ($string_utils:explode(r[1], " ")[1] == "error")) break; else player:tell("Unexpected response in $cvs:", verb, ": tell phil (", toliteral(r), ")"); break; endif endfor return E_INVARG; . @verb $cvs:"add_entry remove_entry" this none this xd @program $cvs:add_entry "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; object = args[1]; entry = args[2]; if (object.cvs_data[3][1] != "Entries") return E_INVARG; endif e = $string_utils:explode(entry, "/", 1); name = (("/" + e[2]) + "/") + e[3]; entries = object.cvs_data[3][2]; for a in [1..length(entries)] i = index(entries[a], name); if ((i == 1) || (i == 2)) if (verb == "add_entry") entries[a] = entry; object.cvs_data[3][2] = entries; else object.cvs_data[3][2] = listdelete(object.cvs_data[3][2], a); endif return; endif endfor if (verb == "add_entry") entries = {@entries, entry}; object.cvs_data[3][2] = entries; endif return 1; . @verb $cvs:"get_entry" this none this xd @program $cvs:get_entry "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; object = args[1]; name = args[2]; if (object.cvs_data[3][1] != "Entries") return E_INVARG; endif entries = object.cvs_data[3][2]; for a in [1..length(entries)] i = index(entries[a], name + "/"); if ((i == 1) || (i == 2)) return entries[a][i..$]; endif endfor return ""; . @verb $cvs:"_ci" this none this xd @program $cvs:_ci "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; cvsroot = this:normalize_cvsroot(args[2]); params = args[3]; items = args[4]; logmsg = {}; options = {}; for p in (params) if (!strcmp(p[1], "m")) logmsg = p[2]; elseif (!strcmp(p[1], "F")) player:tell(" option -F not implemented yet."); return; else options = {@options, ("Argument -" + p[1]) + p[2]}; endif endfor error = 0; for item in (items) $command_utils:suspend_if_needed(0); {type, object, name} = this:parse_objstr(item); if (type == "error") return E_INVARG; endif if (!object.cvs_data) player:tell("cvs [commit aborted]: there is no version here; do 'cvs checkout' first"); return; endif if ((type == "verb") && (!$object_utils:has_verb(object, name))) player:tell("cvs commit: nothing known about `", object, ":", name, "'"); error = 1; elseif ((type == "prop") && (!$object_utils:has_property(object, name))) player:tell("cvs commit: nothing known about `", object, ".", name, "'"); error = 1; endif if (error) continue; endif {server, root, user} = this:parse_cvsroot(cvsroot); repository = root + object.cvs_data[2][2]; this:write(conn, "Argument -m"); x = ""; for line in (logmsg) this:write(conn, "Argument", x, " ", line); x = "x"; endfor for o in (options) this:write(conn, o); endfor retval = this:send_entries_data(conn, repository, type, object, name); if (typeof(retval) != ERR) this:write(conn, "ci"); l = this:read_response(conn); resp = this:parse_response(@l); resp = this:handle_common_responses(object, resp); for r in (resp) if ((length(r) == 1) && ($string_utils:explode(r[1], " ")[1] == "error")) break; else player:tell("Unexpected response in $cvs:", verb, ": tell phil (", toliteral(r), ")"); return; endif endfor endif endfor if (error) player:tell("cvs [commit aborted]: correct above errors first!"); endif . @verb $cvs:"parse_response" this none this xd @program $cvs:parse_response "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; parsed = queue = {}; for line in (args) if (line) command = $string_utils:explode(line, " ")[1]; else command = line; endif if (command in this.valid_responses) if (queue) "store the last command before starting this new one"; parsed = {@parsed, queue}; queue = {}; endif if (command == "ok") continue; endif queue = {line}; else if (!queue) player:tell(">>> expected a command, got \"", line, "\""); else queue = {@queue, line}; endif endif endfor if (queue) parsed = {@parsed, queue}; endif return parsed; . @verb $cvs:"_status_usage" this none this xd @program $cvs:_status_usage player:tell("Usage: cvs status [-v] objects|properties|verbs..."); player:tell(" -v Verbose format; includes tag information for the file"); player:tell("(Specify the --help global option for a list of other help options)"); . @verb $cvs:"_update_usage" this none this xd @program $cvs:_update_usage player:tell("Usage: cvs update [-ACdfp] [-r rev|-D date] [-j rev]"); player:tell(" objects|properties|verbs..."); player:tell(" -A Reset any sticky tags/date/kopts."); player:tell(" -C Overwrite locally modified items with clean repository copies."); player:tell(" -d Add new items, like checkout does."); player:tell(" -f Force a head revision match if tag/date not found."); player:tell(" -p Send updates to standard output (avoids stickiness)."); player:tell(" -r rev Update using specified revision/tag (is sticky)."); player:tell(" -D date Set date to update from (is sticky)."); player:tell(" -j rev Merge in changes made between current revision and rev."); player:tell("(Specify the --help global option for a list of other help options)"); . @verb $cvs:"parse_objstr" this none this xd @program $cvs:parse_objstr "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; item = args[1]; silent = 0; if (length(args) > 1) silent = args[2]; endif type = ""; "is 'item' an object?"; if (((!index(item, ":")) && (!index(item, "."))) && valid(object = player:my_match_object(item))) type = "object"; thing = object; else "how about a verb or propref?"; vr = $code_utils:parse_verbref(item); if (vr) "verb!"; objname = vr[1]; thing = vr[2]; type = "verb"; else pr = $code_utils:parse_propref(item); if (pr) "property!"; objname = pr[1]; thing = pr[2]; type = "prop"; else if (!silent) player:tell("You must supply objects, verbs, or properties."); endif return {"error", #-1, #-1}; endif endif if ($command_utils:object_match_failed(object = player:my_match_object(objname), objname)) return {"error", #-1, #-1}; endif endif if (this.debug) player:tell("object: ", object.name, " (", object, ")"); if (type != "object") player:tell(type, ": ", thing); endif endif return {type, object, thing}; . @verb $cvs:"object_to_entries" this none this xd @program $cvs:object_to_entries "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; type = args[1]; object = args[2]; item = args[3]; entries = {}; if (type == "object") for p in (properties(object)) e = this:get_entry(object, "/props/" + p); if (!e) e = ("QU/props/" + p) + "////"; endif entries = {@entries, "U" + e}; endfor for v in (verbs(object)) e = this:get_entry(object, "/verbs/" + v); if (!e) e = ("QU/verbs/" + v) + "////"; endif entries = {@entries, "U" + e}; endfor else e = this:get_entry(object, (("/" + type) + "s/") + item); if (!e) e = ((("Q/" + type) + "s/") + item) + "////"; endif entries = {@entries, e}; endif return entries; . @verb $cvs:"_annotate _diff _log _rdiff _remove _rtag _status _tag _update" this none this xd @program $cvs:_annotate "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; conn = args[1]; cvsroot = this:normalize_cvsroot(args[2]); params = args[3]; items = args[4]; command = verb[2..$]; if ((command == "co") && (!items)) "Ok, just one teeny tiny special case."; for p in (params) this:write(conn, "Argument -", p[1], p[2]); endfor this:write(conn, command); l = this:read_response(conn); return; endif if (!items) return E_INVARG; elseif (command == "tag") "items[1] is the tag name"; tagname = items[1]; items = listdelete(items, 1); if (!items) return E_INVARG; endif endif for item in (items) $command_utils:suspend_if_needed(0); {type, object, name} = this:parse_objstr(item); if (type == "error") return E_INVARG; endif if (!object.cvs_data) player:tell("cvs [", command, " aborted]: there is no version here; do `cvs checkout' first or use `cvs add' to create an entry"); return; endif {server, root, user} = this:parse_cvsroot(cvsroot); repository = root + object.cvs_data[2][2]; if (command == "remove") if ({"f", ""} in params) retval = this:remove_file(object, (("/" + type) + "s/") + name); if (typeof(retval) == ERR) return; endif endif else for p in (params) this:write(conn, "Argument -", p[1], p[2]); endfor endif if (command == "tag") this:write(conn, "Argument ", tagname); endif retval = this:send_entries_data(conn, repository, type, object, name); if (typeof(retval) != ERR) this:write(conn, command); l = this:read_response(conn); resp = this:parse_response(@l); resp = this:handle_common_responses(object, resp); for r in (resp) if ((length(r) == 1) && ($string_utils:explode(r[1], " ")[1] == "error")) break; else player:tell("Unexpected response in $cvs:", verb, ": tell phil (", toliteral(r), ")"); break; endif endfor endif endfor . @verb $cvs:"_diff_usage" this none this xd @program $cvs:_diff_usage player:tell("Usage: cvs diff [-N] [rcsdiff-options]"); player:tell(" [[-r rev1 | -D date1] [-r rev2 | -D date2]] objects|properties|verbs..."); player:tell(" -D d1 Diff revision for date against working item."); player:tell(" -D d2 Diff rev1/date1 against date2."); player:tell(" -N include diffs for added and removed items."); player:tell(" -r rev1 Diff revision for rev1 against working file."); player:tell(" -r rev2 Diff rev1/date1 against rev2."); player:tell("(consult the documentation for your diff program for rcsdiff-options."); player:tell("The most popular is -c for context diffs but there are many more)."); player:tell("(Specify the -h global option for a list of other help options)"); . @verb $cvs:"count_bytes" this none this xd @program $cvs:count_bytes bytes = 0; for line in (args) "+1 for the n"; bytes = (bytes + length(line)) + 1; endfor return bytes; . @verb $cvs:"send_entries_data" this none this xd @program $cvs:send_entries_data "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; {conn, repository, type, object, item} = args; "We need to do something slightly special if this is a commit, so do a little"; "research before we begin"; commit = callers()[2][2] == "_ci"; spec = unspec = {}; arguments = {}; if (type == "object") "When a user provides an object, he's really providing all of the verbs and"; "properties defined on that object. But he's also providing any of his"; "inherited verbs/props that he's explicitly done a 'cvs add' on."; for p in (properties(object)) $command_utils:suspend_if_needed(0); unspec = {@unspec, "props/" + p}; endfor for v in (verbs(object)) $command_utils:suspend_if_needed(0); unspec = {@unspec, "verbs/" + v}; endfor for e in (object.cvs_data[3][2]) $command_utils:suspend_if_needed(0); entry = $string_utils:explode(e, "/", 1); path = (entry[2] + "/") + entry[3]; if (!(path in unspec)) unspec = {@unspec, path}; endif arguments = {@arguments, entry[2]}; endfor else name = (type + "s/") + item; spec = {@spec, name}; arguments = {name}; endif if (this.debug) player:tell("spec : ", toliteral(spec)); player:tell("unspec: ", toliteral(unspec)); endif for path in ({@spec, @unspec}) {dir, name} = $string_utils:explode(path, "/"); this:write(conn, "Directory ", dir); this:write(conn, (repository + "/") + dir); data = {}; e = this:get_entry(object, "/" + path); if (e) entry = $string_utils:explode(e, "/", 1); data = {"Entry /" + $string_utils:from_list(entry[3..$], "/")}; elseif (!(path in spec)) data = {"Questionable " + name}; elseif (commit) player:tell("cvs commit: use `cvs add' to create an entry for ", item); player:tell("cvs [commit aborted]: correct above errors first!"); return E_INVARG; endif if ((path in spec) || e) tmpdata = this:get_data(object, path); if (typeof(tmpdata) == ERR) player:tell("cvs [aborted]: error getting data"); this:close(conn); return E_INVARG; endif if (tmpdata) bytes = this:count_bytes(@tmpdata); tmpdata = {tostr(bytes), @tmpdata}; {dir, name} = $string_utils:explode(path, "/"); tmpdata = {"Modified " + name, "u=rw,g=rw,o=r", @tmpdata}; endif data = {@data, @tmpdata}; endif for line in (data) $command_utils:suspend_if_needed(0); this:write(conn, line); endfor endfor if (arguments) this:write(conn, "Directory ."); this:write(conn, repository); for a in ($list_utils:remove_duplicates(arguments)) this:write(conn, "Argument ", a); endfor endif return 0; . @verb $cvs:"handle_common_responses" this none this xd @program $cvs:handle_common_responses "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; object = args[1]; resp = args[2]; editor = 0; if (length(args) > 2) editor = args[3]; endif retval = {}; "Which commands we're going to handle in this function and how many lines they"; "should be. 0 means a variable number of lines"; understood = {{"Clear-static-directory", 2}, {"Set-static-directory", 2}, {"checked-in", 3}, {"remove-entry", 2}, {"copy-file", 3}, {"created", 0}, {"updated", 0}, {"merged", 0}, {"rcs-diff", 0}}; for r in (resp) $command_utils:suspend_if_needed(0); command = $string_utils:explode(r[1], " ")[1]; i = $list_utils:iassoc(command, understood); if ((i && (understood[i][2] != 0)) && (length(r) != understood[i][2])) player:tell("Horked `", command, "' command received: expected ", understood[i][2], " lines, got ", length(r)); continue; endif if (command == "Checked-in") "r[3] is the new Entries entry"; dir = $string_utils:basename($string_utils:dirname(r[2])); new_entry = ("/" + dir) + r[3]; this:add_entry(object, new_entry); elseif (command == "Remove-entry") "r[2] is the file"; tmp = $string_utils:explode(r[2], "/"); path = (("/" + tmp[$ - 1]) + "/") + tmp[$]; this:remove_entry(object, path); elseif (command == "Clear-sticky") "r[2] is the directory (verbs or props) to clear. Clear all stickiness for"; "items of that type."; dir = $string_utils:basename(r[2]); for entry in (object.cvs_data[3][2]) $command_utils:suspend_if_needed(0); e = $string_utils:explode(entry, "/", 1); if (e[2] != dir) continue; endif e[$] = ""; new_entry = $string_utils:from_list(e, "/"); this:add_entry(object, new_entry); endfor elseif (command == "Removed") "r[2] is the file"; tmp = $string_utils:explode(r[2], "/"); path = (("/" + tmp[$ - 1]) + "/") + tmp[$]; this:remove_entry(object, path); this:remove_file(object, path); elseif (((command == "Created") || (command == "Updated")) || (command == "Merged")) "r[2] is the file"; "r[3] is the new Entries entry"; "r[4] is the mode data, which we can ignore"; "r[5] is the byte count, including `n's"; "the remainder is file data"; dir = $string_utils:basename($string_utils:dirname(r[2])); new_entry = ("/" + dir) + r[3]; bytes = r[5]; data = r[6..$]; tmp = this:add_or_update_file(object, new_entry, data, editor); if (typeof(tmp) != ERR) this:add_entry(object, new_entry); endif elseif (command == "Copy-file") "r[2] is the file"; "r[3] is the new name"; tmp = $string_utils:explode(r[2], "/"); path = (("/" + tmp[$ - 1]) + "/") + tmp[$]; newname = (("/" + tmp[$ - 1]) + "/") + r[3]; this:add_or_update_file(object, newname, this:get_data(object, path), editor); elseif (command == "Rcs-diff") "r[2] is the file"; "r[3] is the new Entries entry"; "r[4] is the mode data, which we can ignore"; "r[5] is the byte count, including `n's"; "the remainder is file data"; dir = $string_utils:basename($string_utils:dirname(r[2])); new_entry = ("/" + dir) + r[3]; bytes = r[5]; patch = r[6..$]; data = this:apply_rcs_diff(this:get_data(new_entry), patch); retval = this:add_or_update_file(object, new_entry, data, editor); if (typeof(retval) != ERR) this:add_entry(object, new_entry); endif elseif ((command == "Clear-static-directory") || (command == "Set-static-directory")) "Do nothing"; else retval = {@retval, r}; endif endfor return retval; . @verb $cvs:"resolve_command" this none this xd @program $cvs:resolve_command "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; command = args[1]; for i in [1..length(this.commands)] comm = this.commands[i]; if (command == comm) return comm; endif for pattern in (this.command_aliases[i]) if (pattern == command) return comm; endif if (star = index(pattern, "*")) if (index(command, pattern[1..star - 1]) == 1) return comm; endif endif endfor endfor return E_INVARG; . @verb $cvs:"add_or_update_file" this none this xd @program $cvs:add_or_update_file "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; {object, entry, data, editor} = args; e = $string_utils:explode(entry, "/", 1); type = e[2][1..4]; name = e[3]; if (type == "verb") {junk, owner} = $string_utils:explode(data[1], ":"); {junk, perms} = $string_utils:explode(data[2], ":", 1); {junk, verbargs} = $string_utils:explode(data[3], ":"); owner = toobj(owner); verbargs = $string_utils:explode(verbargs, " "); if (!$object_utils:has_verb(object, name)) x = `add_verb(object, {owner, perms, name}, verbargs) ! ANY'; if (typeof(x) == ERR) player:tell("cvs [aborted]: could not add verb ", object, ":", name, ": ", x); return E_INVARG; endif endif "Try to set_verb_code first; if compilation fails we don't want to do the "; "other things."; x = `set_verb_code(object, name, data[4..$]) ! ANY'; if (typeof(x) == ERR) player:tell("cvs [aborted]: could not set verb code ", object, ":", name, ": ", x); return E_INVARG; endif if (x) player:tell(" >>> Compilation failed. Run again with the -e flag to open the verb editor with the new code."); return E_TYPE; endif x = `set_verb_args(object, name, verbargs) ! ANY'; if (typeof(x) == ERR) player:tell("cvs [aborted]: could not set verb arguments ", object, ":", name, " to ", verbargs, ": ", x); return E_INVARG; endif try info = verb_info(object, name); info[1] = owner; info[2] = perms; set_verb_info(object, name, info); except x (ANY) player:tell("cvs [aborted]: could not set verb info ", object, ":", name, " to ", toliteral(info), ": ", x); return E_INVARG; endtry else {junk, owner} = $string_utils:explode(data[1], ":"); {junk, perms} = $string_utils:explode(data[2], ":", 1); {junk, type} = $string_utils:explode(data[3], ":"); owner = toobj(owner); if (!$object_utils:has_property(object, name)) x = `add_property(object, name, {}, {owner, perms}) ! ANY'; if (typeof(x) == ERR) player:tell("cvs [aborted]: could not add property ", object, ":", name, ": ", x); return E_INVARG; endif endif try info = property_info(object, name); info[1] = owner; info[2] = perms; set_property_info(object, name, info); except x (ANY) player:tell("cvs [aborted]: could not set property info ", object, ":", name, " to ", toliteral(info), ": ", x); return E_INVARG; endtry if (type == LIST) new = {}; for line in (data[4..$]) $command_utils:suspend_if_needed(0); new = {@new, eval(data[4])}; endfor object.(name) = new; else object.(name) = eval(data[4]); endif endif return 0; . @verb $cvs:"_cvs" any any any x @program $cvs:_cvs "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; {param, commstr, args} = this:parse_args("d:hHcvlnqQt", args[1]); if (typeof(param) == ERR) this:_cvs_usage(); return; endif cvsroot = player.cvsroot; usage_only = 0; options = {}; "Handle CVS global options"; for p in (param) if (!strcmp(p[1], "d")) cvsroot = param[l][2]; elseif (!strcmp(p[1], "h")) return this:_cvs_usage(); elseif (!strcmp(p[1], "H")) usage_only = 1; elseif (!strcmp(p[1], "c")) return this:_cvs_commands(); elseif (!strcmp(p[1], "v")) player:tell(); player:tell("Concurrent Versions System (CVS) for MOO version ", this.version, " (client)"); player:tell("http://www.off.net/moocvs/"); player:tell(); player:tell("Copyright (c) 2001 Phil Schwan "); player:tell(); player:tell("CVS may be used, modified, and redistributed under the terms of the BSD"); player:tell("license. Visit the above URL for more information."); player:tell(); player:tell("Specify the -h option for further information about CVS"); return; elseif (index("lnqQt", p[1], 1)) options = {@options, "Global_option -" + p[1]}; if (p[1] == "Q") "-Q implies -q"; options = {@options, "Global_option -q"}; endif endif endfor if (!commstr) player:tell("You must specify a command."); this:_cvs_usage(); return; endif "Resolve aliases such as 'up' into 'update' and so on. Note that the command"; "name is the name as specified in the wildly-inconsistent CVS protocol--update,"; "for example, is spelled out, but checkout and commit are 'co' and 'ci'"; "respectively. Go team!"; command = this:resolve_command(commstr); if (typeof(command) == ERR) player:tell("Unknown command: `", commstr, "'"); player:tell(); this:_cvs_commands(); return; endif if (usage_only) this:(("_" + command) + "_usage")(); return; endif if (!cvsroot) player:tell("cvs ", command, ": no CVSROOT specified! Please use the `-d' option"); player:tell("cvs [", command, " aborted]: or set the player.cvsroot variable."); return; endif "Login is something of a special case, since it has a different kind of"; "handshake. (Awesome CVS protocol strikes again!)"; if (command == "login") this:_login(cvsroot); player:tell("cvs: done"); return; elseif (command == "logout") this:_logout(cvsroot); player:tell("cvs: done"); return; endif comm_index = command in this.commands; optstr = this.command_opts[comm_index]; {opts, other} = this:getopt(optstr, args); if (typeof(opts) == ERR) this:(("_" + command) + "_usage")(); return; endif "Verbs prefer that their params all have two arguments"; for i in [1..length(opts)] if (length(opts[i]) < 2) opts[i] = {@opts[i], ""}; endif endfor conn = this:open_connection(cvsroot); if (typeof(conn) == ERR) player:tell("cvs: Unable to establish a connection with the specified server."); return; endif "Write any global options to the socket"; for opt in (options) this:write(conn, opt); endfor if (this.debug) player:tell("opts : ", toliteral(opts)); player:tell("other: ", toliteral(other)); endif "Some commands requires some option preprocessing or other non-standard"; "hijinks"; if (command == "co") for o in (opts) if (!strcmp(o[1], "c")) "-c implies -N (it doesn't follow logically, but I do what cvs does)"; opts = {@opts, {"N", ""}}; endif endfor elseif (command == "commit") logmsg = ""; for i in [1..length(opts)] if (!strcmp(opts[i][1], "m")) logmsg = {opts[i][2]}; opts[i][2] = logmsg; endif endfor if (!logmsg) player:tell("Please enter a commit log entry."); logmsg = $command_utils:read_lines(); opts = {@opts, {"m", logmsg}}; endif elseif (command == "update") opts = {@opts, {"P", ""}, {"u", ""}}; endif "Do it do it do it."; if (typeof(this:("_" + command)(conn, cvsroot, opts, other)) == ERR) this:(("_" + command) + "_usage")(); endif this:close(conn); player:tell("cvs: done"); . @verb $cvs:"_cvs_usage" this none this xd @program $cvs:_cvs_usage player:tell("Usage: cvs [cvs-options] command [command-options-and-arguments]"); player:tell(); player:tell("CVS global options (specified before the command name) are:"); player:tell(" -h Displays this message."); player:tell(" -H Displays usage information for command."); player:tell(" -c Displays a list of commands."); player:tell(" -Q Cause CVS to be really quiet."); player:tell(" -q Cause CVS to be somewhat quiet."); player:tell(" -l Turn history logging off."); player:tell(" -n Do not execute anything that will change the disk."); player:tell(" -t Show trace of program execution -- try with -n."); player:tell(" -v CVS version and copyright."); player:tell(" -d CVS_root Overrides $CVSROOT as the root of the CVS tree."); player:tell(" -f Do not use your cvsrc property."); . @verb $cvs:"_cvs_commands" this none this xd @program $cvs:_cvs_commands "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; player:tell("CVS commands understood by this client are:"); for i in [1..length(this.commands)] help = this.command_short_help[i]; "The first alias is the de-facto command name (sigh)"; player:tell(" ", $string_utils:left(this.command_aliases[i][1], 13), help); endfor . @verb $cvs:"_ci_usage" this none this xd @program $cvs:_ci_usage player:tell("Usage: cvs commit [-nRf] [-m msg | -F log property] objects|properties|verbs..."); player:tell(" -n Do not run the module program (if any)."); player:tell(" -f Force the file to be committed."); player:tell(" -F prop Read the log message from a list property."); player:tell(" -m msg Log message."); player:tell(" -r rev Commit to this branch or trunk revision."); player:tell("(Specify the -h global option for a list of other help options)"); . @verb $cvs:"_remove_usage" this none this xd @program $cvs:_remove_usage player:tell("Usage: cvs remove [-f] objects|properties|verbs..."); player:tell(" -f Delete the item before removing it. Is only a valid option when"); player:tell(" providing properties or verbs--it will not delete an object."); player:tell("(Specify the -h global option for a list of other help options)"); . @verb $cvs:"apply_rcs_diff" this none this xd @program $cvs:apply_rcs_diff "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; {source, diff} = args; line_delta = 0; i = 1; size = length(diff); while (i <= size) $command_utils:suspend_if_needed(0); line = diff[i]; {where, count} = $string_utils:explode(line, " "); where = tonum(where[2..$]); count = tonum(count); if (line[1] == "a") for tmp in [1..count] source = listinsert(source, diff[i + 1], (where + line_delta) + 1); line_delta = line_delta + 1; i = i + 1; endfor elseif (line[1] == "d") for tmp in [1..count] source = listdelete(source, where + line_delta); endfor line_delta = line_delta - count; endif i = i + 1; endwhile return source; . @verb $cvs:"get_data" this none this xd @program $cvs:get_data "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; object = args[1]; path = args[2]; {dir, name} = $string_utils:explode(path, "/"); data = {}; if (dir == "verbs") if ($object_utils:has_verb(object, name)) info = verb_info(object, name); arginfo = verb_args(object, name); data = {tostr("owner:", info[1]), "permissions:" + info[2], "args:" + $string_utils:from_list(arginfo, " ")}; data = {@data, @verb_code(object, name)}; endif elseif (dir == "props") if ($object_utils:has_property(object, name)) info = property_info(object, name); data = {tostr("owner:", info[1]), "permissions:" + info[2], tostr("type:", typeof(object.(name)))}; if (typeof(object.(name)) == LIST) for line in (object.(name)) $command_utils:suspend_if_needed(0); data = {@data, toliteral(line)}; endfor else data = {@data, toliteral(object.(name))}; endif endif endif return data; . @verb $cvs:"remove_file" this none this xd @program $cvs:remove_file "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; {object, entry} = args; e = $string_utils:explode(entry, "/", 1); type = e[2][1..4]; name = e[3]; if (type == "verb") if ($object_utils:has_verb(object, name)) x = `delete_verb(object, name) ! ANY'; if (typeof(x) == ERR) player:tell("cvs: could not remove verb ", object, ":", name, ": ", x); return E_INVARG; endif endif else if ($object_utils:has_property(object, name)) x = `delete_property(object, name) ! ANY'; if (typeof(x) == ERR) player:tell("cvs: could not remove property ", object, ":", name, ": ", x); return E_INVARG; endif endif endif return 0; . @verb $cvs:"_co_usage" this none this xd @program $cvs:_co_usage player:tell("Usage: cvs checkout [-Acfnps] [-r rev | -D date] [-j rev1]"); player:tell(" [-j rev2] <-d object> path/object"); player:tell(" -d obj Checkout into this object."); player:tell(" -A Reset any sticky tags/dates/kopts."); player:tell(" -c \"cat\" the module database."); player:tell(" -f Force a head revision match if tag/date not found."); player:tell(" -n Do not run module program (if any)."); player:tell(" -p Check out files to standard output (avoids stickiness)."); player:tell(" -s Like -c, but include module status."); player:tell(" -r rev Check out revision or tag. (is sticky)"); player:tell(" -D date Check out revisions as of date. (is sticky)"); player:tell(" -j rev Merge in changes made between current revision and rev."); player:tell("(Specify the -h global option for a list of other help options)"); . @verb $cvs:"getopt" this none this xd @program $cvs:getopt "Copyright (c) 2001 Phil Schwan "; "Visit http://www.off.net/moocvs/ for license and update information."; ""; "acts something like getopt(3), at least in theory. This function was clean"; "and beautiful until I realized that I needed to handle quotes, and now the"; "first half is spaghetti. My deepest apologies, but I can't be fucked to go"; "back and rewrite it now."; ""; "adding two spaces now saves a lot of length() checking later"; optstr = args[1] + " "; "Explode does not take quotation marks into account. Oh well."; if (index(args[2], "\"")) tmp = $string_utils:explode(args[2], "\""); arguments = {}; while (args[2]) "Wow, this loop is, in retrospect, really incomprehensible."; $command_utils:suspend_if_needed(0); a = index(args[2], " "); b = index(args[2], "\""); if ((a && (a < b)) || (a && (!b))) if (a == 1) args[2] = args[2][2..$]; continue; endif arguments = {@arguments, args[2][1..a - 1]}; args[2] = args[2][a + 1..$]; elseif ((b && (b < a)) || (b && (!a))) "remove the first quote"; args[2][b..b] = ""; "seek the next quote"; b = index(args[2], "\""); if (!b) player:tell("error: unbalanced quotes"); return E_INVARG; endif tmp = args[2][1..b - 1]; args[2] = args[2][b + 1..$]; "and then, finally, the first space after that quote"; a = index(args[2], " "); if (a > 1) tmp = tmp + args[2][1..a - 1]; args[2] = args[2][a + 1..$]; endif arguments = {@arguments, tmp}; else "We're done!"; arguments = {@arguments, args[2]}; args[2] = ""; endif endwhile else arguments = $string_utils:explode(args[2], " "); endif if (!arguments) return {{}, {}}; endif opts = {}; newargs = {}; next_argument = "no"; arg_waiting = ""; for a in (arguments) if (next_argument == "required") if (a[1] == "-") player:tell("option requires an argument -- ", arg_waiting); return E_INVARG; endif endif if ((next_argument != "no") && (a[1] != "-")) opts = {@opts, {arg_waiting, a}}; next_argument = "no"; continue; endif if (((a[1] == "-") && (a != "-")) && (a != "--")) if (length(a) > 1) for n in [2..length(a)] i = index(optstr, a[n], 1); if (i == 0) "actually, I decided to just make this an error"; player:tell("invalid option -- ", a[n]); return E_INVARG; "old code:"; opts = {@opts, {"?", a[n]}}; continue; endif if (optstr[i + 1] == ":") if (length(a) > n) "the rest of this -fblahblah is the argument"; opts = {@opts, {a[n], a[n + 1..$]}}; break; endif if (optstr[i + 2] == ":") next_argument = "optional"; else next_argument = "required"; endif arg_waiting = a[n]; else opts = {@opts, {a[n]}}; endif endfor endif else newargs = {@newargs, a}; endif endfor return {opts, newargs}; . @verb $cvs:"_log_usage" this none this xd @program $cvs:_log_usage player:tell("Usage: cvs log [-RhtNb] [-r[revisions]] [-d dates] [-s states]"); player:tell(" [-w[logins]] objects|properties|verbs..."); player:tell(" -R Only print name of RCS file."); player:tell(" -h Only print header."); player:tell(" -t Only print header and descriptive text."); player:tell(" -N Do not list tags."); player:tell(" -b Only list revisions on the default branch."); player:tell(" -r[revisions] Specify revision(s) to list."); player:tell(" -d dates Specify dates (D1"; "Visit http://www.off.net/moocvs/ for license and update information."; cvsroot = this:normalize_cvsroot(args[1]); {server, root, user} = this:parse_cvsroot(cvsroot); player:tell("(Logging out of ", user, "@", server, ")"); cp = $list_utils:iassoc(cvsroot, player.cvspass); if (cp) player.cvspass = listdelete(player.cvspass, cp); else player:tell("Entry not found for ", cvsroot); endif . @verb $cvs:"_login_usage _logout_usage" this none this xd @program $cvs:_login_usage if (verb == "_login_usage") player:tell("Usage: cvs login"); else player:tell("Usage: cvs logout"); endif player:tell("(Specify the -h global option for a list of other help options)"); . @verb $cvs:"_tag_usage" this none this xd @program $cvs:_tag_usage player:tell("Usage: cvs tag [-dfbF] [-r rev|-D date] tag objects|properties|verbs..."); player:tell(" -d Delete the given tag."); player:tell(" -r rev Existing revision/tag."); player:tell(" -D date Existing date."); player:tell(" -f Force a head revision if specified tag not found."); player:tell(" -b Make the tag a \"branch\" tag, allowing concurrent development."); player:tell(" -F Move tag if it already exists."); player:tell(" -c Check that working files are unmodified."); player:tell("(Specify the -h global option for a list of other help options)"); . "***finished***