"__PACKAGE__ is the objectid of the generic MCP package "__NEW__ is the object id of the new object resulting from the @create command "__REGISTRY__ is the value of $mcp.registry "To add the package, become a wizard and type: " @add-package __NEW__ to __REGISTRY__ @create __PACKAGE__ named dns-org-mud-moo-simpleedit:dns-org-mud-moo-simpleedit @prop __NEW__."foo" 1 rc @prop __NEW__."v_filter_in" {} rc ;__NEW__.("v_filter_in") = {__NEW__, "verbcode_external_to_internal"} @prop __NEW__."v_filter_out" {} rc ;__NEW__.("v_filter_out") = {__NEW__, "verbcode_internal_to_external"} ;__NEW__:set_version_range({"1.0", "1.0"}) ;__NEW__:set_messages_in({{"set", {"reference", "type", "content"}}}) ;__NEW__:set_messages_out({{"content", {"reference", "name", "type", "content"}}}) ;__NEW__.("description") = {"A very simple local editing protocol.", "", "S->C #$#dns-org-mud-moo-simpleedit-content (reference, name, type, content*)", "", "Reference is the tag used when sending back to the server.", "User may be allowed to edit it (i.e., save this same text into a", "different property), with possibility of disastrous results.", "", "Name is a human-readable name for the info, suitable for window title,", "buffer name, etc.", "", "Type is one of the following (for version 1.0):", " * string", " * string-list", " * moo-code", "", "Content is the content interpreted according to the type", "info given. It's multiline (hence the *).", "", "clients that don't provide special support for moo-code editing can", "treat moo-code identically to string-list.", "", "C->S #$#dns-org-mud-moo-simpleedit-set (reference, content*, type)", "", "reference, content, and type are as above. This is the message sent by the client to set when the user 'saves' the value. Note this does not necessarily save the value. Errors such as lack of permission to set the given reference or moo-code compliation errors may prevent it. It is expected that the server will tell the user this (in the in-band text stream). ", "", "Clients will probably want to provide a way to just send without closing the window, buffer, etc for this reason.", "", "", "JHCore implementation notes", "", "JHCore currently understands several different kinds of (local) editing sessions:", "handled by $verb_editor:", " * verb editing", "handled by $note_editor:", " * list of strings editing for notes and properties", "handled by both $note_editor and $list_editor(?):", " * value editing for properties", "handled by $mail_editor:", " * sending a mail message", "", "@edit uses a semi-complicated system to determine (a) what the user is trying to edit and (b) how to edit it. ", "", "So it looks like the critical things to modify are $generic_editor:invoke_local_editor, $note_editor:local_editing_info, $verb_editor:local_editing_info, and $mail_editor:local_editing_info. ", "", "The current return path for locally edited stuff (in the core) appears to be:", " @program", " @set-note-text", " @set-note-value", " @@sendmail", "", ".v_filter_in / .v_filter_out are hooks called when receiving/sending verb code. JHCore supports a '// comment' syntax in verbs and conversion is made from '// blah' to '\"blah\";' by using the verbs :verbcode_external_to_internal / :verbcode_internal_to_external.", "", ".v_filter_in / .v_filter_out are both 2 element lists containing an object id and a verb. the JHCore configuration is:", "", " this.v_filter_in = { this, \"verbcode_external_to_internal\" }", " this.v_filter_out = { this, \"verbcode_internal_to_external\" }", "", "Non JHCore installations might choose to set .v_filter_in / .v_filter_out to false (0, \"\" or {}) which leaves code lines unfiltered.", "", ""} @verb __NEW__:"send_content" this none this rxd @program __NEW__:send_content "Usage: :send_content()"; ""; if ($perm_utils:controls(caller_perms(), args[1])) pass(@args); else raise(E_PERM); endif . @verb __NEW__:"handle_set" this none this @program __NEW__:handle_set "Usage: :handle_set(session, reference, type, content)"; ""; {session, reference, type, content} = args; if (caller != this) raise(E_PERM); endif set_task_perms(session.connection); try if (type == "moo-code") rval = this:edit_set_program(reference, content); elseif (reference == "sendmail") rval = this:edit_sendmail(reference, content); else rval = this:edit_set_note_value(reference, type, content); endif player:notify_lines((typeof(rval) == LIST) ? rval | {rval}); except v (ANY) player:notify_lines((typeof(v[2]) == LIST) ? v[2] | {v[2]}); " player:notify_lines($code_utils:format_traceback(v[4], v[2]));"; endtry . @verb __NEW__:"edit_set_program" this none this @program __NEW__:edit_set_program {reference, lines} = args; set_task_perms(caller_perms()); args = $string_utils:words(reference); punt = 1; if (!(spec = $code_utils:parse_verbref(args[1]))) raise(E_INVARG, "Invalid reference: " + reference); elseif ($command_utils:object_match_failed(object = player:my_match_object(spec[1]), spec[1])) return; elseif ($string_utils:is_numeric(spec[2])) "numeric verbref"; if ((verbname = $code_utils:tonum(spec[2])) == E_TYPE) raise(E_INVARG, "Invalid verb number"); elseif (length(args) > 1) raise(E_INVARG, "Invalid reference: " + reference); elseif ((verbname < 1) || `verbname > length(verbs(object)) ! E_PERM => 0') raise(E_INVARG, "Verb number out of range."); else argspec = 0; punt = 0; endif elseif (typeof(argspec = $code_utils:parse_argspec(@listdelete(args, 1))) != LIST) raise(E_INVARG, tostr(argspec)); elseif (argspec[2]) raise(E_INVARG, $string_utils:from_list(argspec[2], " ") + "??"); elseif (length(argspec = argspec[1]) in {1, 2}) raise(E_INVARG, {"Missing preposition", "Missing iobj specification"}[length(argspec)]); else punt = 0; verbname = spec[2]; if (index(verbname, "*") > 1) verbname = strsub(verbname, "*", ""); endif endif "..."; "...if we have an argspec, we'll need to reset verbname..."; "..."; if (punt) elseif (argspec) named = argspec[4..min(5, $)]; argspec = argspec[1..3]; if (!(argspec[2] in {"none", "any"})) argspec[2] = $code_utils:full_prep(argspec[2]); endif loc = $code_utils:find_verb_named_1_based(object, verbname); while (loc && (`verb_args(object, loc) ! E_PERM' != argspec)) loc = $code_utils:find_verb_named_1_based(object, verbname, loc + 1); endwhile if (loc) verbname = loc; else punt = "...can't find it...."; raise(E_INVARG, "That object has no verb matching that name + args."); endif else named = {}; loc = (typeof(verbname) == NUM) ? verbname | 0; endif if (!punt) try info = verb_info(object, verbname); except e (ANY) if (e[1] == E_VERBNF) raise(E_INVARG, "That object does not have that verb definition."); else raise(E_INVARG, tostr(e[2])); endif punt = 1; endtry if (!punt) aliases = info[3]; if (!loc) loc = aliases in (verbs(object) || {}); endif endif endif if (punt) return; else "filter the verb?"; if (this.v_filter_in) lines = this.v_filter_in[1]:(this.v_filter_in[2])(lines); endif if (named) code = $code_utils:split_verb_code(lines); lines = {@code[1], @$code_utils:named_args_to_code(named), @code[2]}; endif try result = set_verb_code(object, verbname, lines); except e (ANY) result = e[2] + " "; "just in case some idiot throws an error with an empty string"; endtry what = tostr(object:name(), ":", aliases, @loc ? {"[", loc, "](", loc - 1, ")."} | "[??]"); if (result) if (typeof(result) == STR) return {"Error programming " + what, result, "Verb not programmed."}; else return {"Error programming " + what, @result, tostr(length(result), " error(s)."), "Verb not programmed."}; endif else return {"0 errors.", "Verb programmed."}; endif endif . @verb __NEW__:"edit_sendmail" this none this @program __NEW__:edit_sendmail "See $player:@@sendmail"; set_task_perms(caller_perms()); {reference, msg} = args; end_head = ("" in msg) || (length(msg) + 1); subject = ""; replyto = ""; rcpts = {}; body = msg[end_head + 1..length(msg)]; for i in [1..end_head - 1] line = msg[i]; if (index(line, "Subject:") == 1) subject = $string_utils:trim(line[9..length(line)]); elseif (index(line, "To:") == 1) if (!(rcpts = $mail_agent:parse_address_field(line))) player:notify("No recipients found in To: line"); return; endif elseif (index(line, "Reply-to:") == 1) if ((!(replyto = $mail_agent:parse_address_field(line))) && $string_utils:trim(line[10..length(line)])) player:notify("No address found in Reply-to: line"); return; endif elseif (i = index(line, ":")) player:notify(tostr("Unknown header \"", line[1..i], "\"")); return; else player:notify("Blank line must separate headers from body."); return; endif endfor if (!rcpts) player:notify("No To: line found."); elseif (!(subject || body)) player:notify("Blank message not sent."); else player:notify("Sending..."); result = $mail_agent:send_message(player, rcpts, replyto ? subject | {subject, replyto}, body); if (e = result && result[1]) if (length(result) == 1) player:notify("Mail actually went to no one."); else player:notify(tostr("Mail actually went to ", $mail_agent:name_list(@listdelete(result, 1)), ".")); endif else player:notify(tostr((typeof(e) == ERR) ? e | ("Bogus recipients: " + $string_utils:from_list(result[2])))); player:notify("Mail not sent."); endif endif return {}; . @verb __NEW__:"edit_set_note_value" this none this @program __NEW__:edit_set_note_value set_task_perms(caller_perms()); {reference, type, content} = args; "reference format == [str|val]:#xx[.pname]"; if (!match(reference, "^%(str%|val%):.+")) return {"Malformed reference: " + reference}; else vtype = reference[1..3]; reference = reference[5..$]; if (((vtype == "str") && (type == "string")) && (length(content) <= 1)) text = content ? content[1] | ""; elseif (vtype == "val") value_list = $note_editor:to_value(@content); if (value_list[1]) return {tostr("Error on line ", value_list[1], ": ", value_list[2]), "Value not saved."}; elseif (typeof(value_list) != LIST) return {"Error parsing value: " + value_list, "Value not saved."}; else text = value_list[2]; endif else text = content; endif endif if (spec = $code_utils:parse_propref(reference)) o = $code_utils:toobj(spec[1]); p = spec[2]; if (typeof(o) == OBJ) if ($object_utils:has_callable_verb(o, setter = "set_" + p)) e = o:(setter)(text); else e = o.(p) = text; endif else return {"Malformed reference: You must supply an object number."}; endif if (typeof(e) == ERR) raise(e, tostr("Error: ", e)); else return tostr("Set ", p, " property of ", o.name, " (", o, ")."); endif elseif (typeof(note = $code_utils:toobj(argstr)) == OBJ) o = note; e = note:set_text(text); if (typeof(e) == ERR) return {tostr("Error: ", e)}; else return tostr("Set text of ", o.name, "."); endif else raise(E_INVARG, tostr("Error: Malformed argument to ", verb, ": ", argstr)); endif . @verb __NEW__:"verbcode_external_to_internal" this none this rxd @program __NEW__:verbcode_external_to_internal "Charter: given a block of verb code lines from the user, transform it into code ready to be passed to set_verb_code(). In particular, reverse any transformation made by :verbcode_internal_to_external."; "This version transforms `// foo' comments to `\"foo\";' comments."; lines = args[1]; new_comments = player:list_option("//_comments"); if (!new_comments) return lines; endif newlines = {}; for line in (lines) mat = match(line, "^ *// ?%(.*%)$"); if (mat) comment = substitute("%1", mat); out = $code_utils:commentify({comment}); newlines = {@newlines, out[1]}; else newlines = {@newlines, line}; endif endfor return newlines; . @verb __NEW__:"verbcode_internal_to_external" this none this rxd @program __NEW__:verbcode_internal_to_external "Charter: given a block of verb code from the verb_code() primitive, transform it into its external representation to be presented to the user."; "This version transforms `\"foo\";' comments to `// foo' comments."; lines = args[1]; new_comments = player:list_option("//_comments"); if (!new_comments) return lines; endif newlines = {}; for line in (lines) mat = match(line, "^%( *%)%(\".*\";%)$"); if (mat) blanks = substitute("%1", mat); comment = substitute("%2", mat); uncommented = $code_utils:uncommentify({comment}); out = (blanks + "// ") + uncommented[1]; newlines = {@newlines, out}; else newlines = {@newlines, line}; endif endfor return newlines; .