Skip to content

Instantly share code, notes, and snippets.

@tvdijen
Forked from toddsundsted/composed.moo
Last active July 1, 2018 15:29
Show Gist options
  • Select an option

  • Save tvdijen/76338e36ff5158d71e38b0f6c40061a7 to your computer and use it in GitHub Desktop.

Select an option

Save tvdijen/76338e36ff5158d71e38b0f6c40061a7 to your computer and use it in GitHub Desktop.
LambdaCore Compatible Dump of Composed
@create $root_class named Composed, Component Library:
@prop __OBJECT__."packages" {} "" __PROGRAMMER__
;;__OBJECT__.("packages") = {}
@prop __OBJECT__."provides_cache" [] "" __PROGRAMMER__
@prop __OBJECT__."requires_cache" [] "" __PROGRAMMER__
@prop __OBJECT__."last_fetch_index" 1529767110 "" __PROGRAMMER__
@prop __OBJECT__."archived" [] "" __PROGRAMMER__
@prop __OBJECT__."cached" [] "" __PROGRAMMER__
@prop __OBJECT__."archive_sources" {} "" __PROGRAMMER__
;;__OBJECT__.("archive_sources") = {"http://stunt.io/v2/packages.json"}
@verb __OBJECT__:"_log" this none this xd __WIZARD__
@program __OBJECT__:_log
(caller != this) && raise(E_PERM);
/* Log args -- for debugging. */
notify(player, tostr(@args));
.
@verb __OBJECT__:"_notify" this none this xd __WIZARD__
@program __OBJECT__:_notify
(caller != this) && raise(E_PERM);
{what, @messages} = args;
/* Send `messages' to `what' for output. Prefer a `notify()' verb over
* the `notify()' built-in, but remember that this still has to work
* during bootstrapping and with legacy code.
*/
if (`what.alt_display_options ! E_PROPNF' == E_PROPNF)
message = "";
stack = messages;
while (stack)
{top, @stack} = stack;
if (typeof(top) == LIST)
{_, _, @body} = top;
stack = {@body, @stack};
else
message = tostr(message, top);
endif
endwhile
`notify(what, message) ! ANY';
else
`what:notify(@messages) ! ANY';
endif
.
@verb __OBJECT__:"_move" this none this xd __WIZARD__
@program __OBJECT__:_move
(caller != this) && raise(E_PERM);
{what, where} = args;
/* Move `what' to `where'. Runs with wiz-perms. Won't raise an error. */
`move(what, where) ! ANY';
.
@verb __OBJECT__:"_suspend_if_necessary" this none this xd __PROGRAMMER__
@program __OBJECT__:_suspend_if_necessary
((ticks_left() < 5000) || (seconds_left() < 2)) && suspend(0);
.
@verb __OBJECT__:"_parse_specifier" this none this xd __PROGRAMMER__
@program __OBJECT__:_parse_specifier
{specifier} = args;
(caller != this) && raise(E_PERM);
if (r = match(specifier, "^%([0-9]+%.[0-9]+%.[0-9]+%),%([_a-zA-Z0-9]+%)$"))
version = specifier[r[3][1][1]..r[3][1][2]];
identifier = specifier[r[3][2][1]..r[3][2][2]];
return {identifier, version};
else
raise(E_INVARG, tostr("Invalid package specifier: ", specifier));
endif
.
@verb __OBJECT__:"_parse_operation" this none this xd __PROGRAMMER__
@program __OBJECT__:_parse_operation
{operation} = args;
(caller != this) && raise(E_PERM);
if (r = match(operation, "^%(<=%|>=%|<%|>%|=%)? *%([0-9]+%)?%(%.%([0-9]+%)%)?%(%.%([0-9]+%)%)?$"))
op = operation[r[3][1][1]..r[3][1][2]] || "=";
major = operation[r[3][2][1]..r[3][2][2]];
minor = operation[r[3][4][1]..r[3][4][2]];
build = operation[r[3][6][1]..r[3][6][2]];
return {op, major, minor, build};
else
raise(E_INVARG, tostr("Invalid operation: ", operation));
endif
.
@verb __OBJECT__:"_parse_version" this none this xd __PROGRAMMER__
@program __OBJECT__:_parse_version
{version} = args;
(caller != this) && raise(E_PERM);
if (r = match(version, "^%([0-9]+%)%(%.%([0-9]+%)%)%(%.%([0-9]+%)%)$"))
major = version[r[3][1][1]..r[3][1][2]];
minor = version[r[3][3][1]..r[3][3][2]];
build = version[r[3][5][1]..r[3][5][2]];
return {major, minor, build};
else
raise(E_INVARG, tostr("Invalid version: ", version));
endif
.
@verb __OBJECT__:"_compare_versions" this none this xd __PROGRAMMER__
@program __OBJECT__:_compare_versions
{ver1, ver2} = args;
(caller != this) && raise(E_PERM);
res = 0;
if (ver1[1] && ver2[1])
if (((!(res = toint(ver1[1]) - toint(ver2[1]))) && ver1[2]) && ver2[2])
if (((!(res = toint(ver1[2]) - toint(ver2[2]))) && ver1[3]) && ver2[3])
res = toint(ver1[3]) - toint(ver2[3]);
endif
endif
endif
return res;
.
@verb __OBJECT__:"_sort_versions" this none this xd __PROGRAMMER__
@program __OBJECT__:_sort_versions
{vers} = args;
(caller != this) && raise(E_PERM);
l = length(vers);
i = 1;
while (i <= l)
v = vers[i];
j = i - 1;
while (j > 0)
if (this:_compare_versions(vers[j], v) >= 0)
break;
endif
vers[j + 1] = vers[j];
j = j - 1;
endwhile
vers[j + 1] = v;
i = i + 1;
endwhile
return vers;
.
@verb __OBJECT__:"_match match" this none this xd __PROGRAMMER__
@program __OBJECT__:_match
{identifier, @args} = args;
if (args && (typeof(args[$]) == MAP))
patterns = args[1..$ - 1];
provides = args[$];
else
patterns = args;
provides = this.provides_cache;
endif
if ((patterns && (length(patterns) == 1)) && (typeof(patterns[1]) == LIST))
patterns = patterns[1];
endif
if ((versions = `provides[identifier] ! E_RANGE => $nothing') == $nothing)
return {$failed_match};
endif
operations = {};
for pattern in (patterns)
this:_suspend_if_necessary();
operations = {@operations, this:_parse_operation(pattern)};
endfor
for _, version in (versions)
this:_suspend_if_necessary();
v1 = this:_parse_version(version);
for operation in (operations)
this:_suspend_if_necessary();
{op, @v2} = operation;
if (((((("=" == op) && this:_compare_versions(v1, v2)) || ((">=" == op) && (this:_compare_versions(v1, v2) < 0))) || (("<=" == op) && (this:_compare_versions(v1, v2) > 0))) || ((">" == op) && (this:_compare_versions(v1, v2) <= 0))) || (("<" == op) && (this:_compare_versions(v1, v2) >= 0)))
versions = mapdelete(versions, version);
break;
endif
endfor
endfor
if (length(versions) < 1)
return {$failed_match};
elseif (length(versions) > 1)
return {$ambiguous_match};
else
version = mapkeys(versions)[1];
object = mapvalues(versions)[1][1];
return {object, identifier, version};
endif
.
@verb __OBJECT__:"_map_specifier" this none this xd __PROGRAMMER__
@program __OBJECT__:_map_specifier
{MAP, specifier, object} = args;
(caller != this) && raise(E_PERM);
if (typeof(specifier) == LIST)
{identifier, version} = specifier;
else
{identifier, version} = this:_parse_specifier(specifier);
endif
(identifier in mapkeys(MAP)) || (MAP[identifier] = []);
(version in mapkeys(MAP[identifier])) || (MAP[identifier][version] = {});
MAP[identifier][version] = setadd(MAP[identifier][version], object);
return MAP;
.
@verb __OBJECT__:"_unmap_specifier" this none this xd __PROGRAMMER__
@program __OBJECT__:_unmap_specifier
{MAP, specifier, object} = args;
(caller != this) && raise(E_PERM);
if (typeof(specifier) == LIST)
{identifier, version} = specifier;
else
{identifier, version} = this:_parse_specifier(specifier);
endif
((identifier in mapkeys(MAP)) && (version in mapkeys(MAP[identifier]))) && (MAP[identifier][version] = setremove(MAP[identifier][version], object));
`MAP[identifier][version] ! E_RANGE => 1' || (MAP[identifier] = mapdelete(MAP[identifier], version));
`MAP[identifier] ! E_RANGE => 1' || (MAP = mapdelete(MAP, identifier));
return MAP;
.
@verb __OBJECT__:"_install" this none this xd __PROGRAMMER__
@program __OBJECT__:_install
/* Updates $composed data structures that track installed packages.
* Assumes that all prerequisites have been checked and are met.
* Rolls back changes if an error occurs. Used by
* $composed:install() and $composed:reinitialize().
*/
(caller != this) && raise(E_PERM);
{identifier, version, object} = args;
try
error = 1;
packages_backup = this.packages;
provides_cache_backup = this.provides_cache;
requires_cache_backup = this.requires_cache;
this.packages = setadd(this.packages, object);
this.provides_cache = this:_map_specifier(this.provides_cache, {identifier, version}, object);
for provides in (`object.provides ! E_INVIND, E_TYPE, E_PROPNF => {}')
this.provides_cache = this:_map_specifier(this.provides_cache, provides, object);
endfor
/* add a record for every installed package I require */
/* match() must succeed because all prerequisites should have been checked */
for requires in (`object.requires ! E_INVIND, E_TYPE, E_PROPNF => {}')
provider = this:_match(@requires, this.provides_cache);
this.requires_cache = this:_map_specifier(this.requires_cache, provider[2..3], object);
endfor
error = 0;
finally
/* after cleanup, allow the error to propagate upward */
if (error)
this.packages = packages_backup;
this.provides_cache = provides_cache_backup;
this.requires_cache = requires_cache_backup;
endif
endtry
.
@verb __OBJECT__:"_uninstall" this none this xd __PROGRAMMER__
@program __OBJECT__:_uninstall
/* Updates $composed data structures that track installed packages.
* Assumes that all prerequisites have been checked and are met.
* Rolls back changes if an error occurs. Used by
* $composed:uninstall() and $composed:delete().
*/
(caller != this) && raise(E_PERM);
{identifier, version, object} = args;
try
error = 1;
packages_backup = this.packages;
provides_cache_backup = this.provides_cache;
requires_cache_backup = this.requires_cache;
this.packages = setremove(this.packages, object);
this.provides_cache = this:_unmap_specifier(this.provides_cache, {identifier, version}, object);
for provides in (`object.provides ! E_INVIND, E_TYPE, E_PROPNF => {}')
this.provides_cache = this:_unmap_specifier(this.provides_cache, provides, object);
endfor
/* remove all records for all installed packages I require (don't use match here because we need to remove a specific record) */
/* _find_requires_provider() must succeed because all prerequisites should have been checked */
for requires in (`object.requires ! E_INVIND, E_TYPE, E_PROPNF => {}')
provider = this:_find_requires_provider(requires[1], object);
this.requires_cache = this:_unmap_specifier(this.requires_cache, provider[2..3], object);
endfor
error = 0;
finally
/* after cleanup, allow the error to propagate upward */
if (error)
this.packages = packages_backup;
this.provides_cache = provides_cache_backup;
this.requires_cache = requires_cache_backup;
endif
endtry
.
@verb __OBJECT__:"_find_requires_provider" this none this xd __PROGRAMMER__
@program __OBJECT__:_find_requires_provider
{identifier, object} = args;
(caller != this) && raise(E_PERM);
for _, version in (this.requires_cache[identifier])
if (object in this.requires_cache[identifier][version])
return {this.provides_cache[identifier][version][1], identifier, version};
endif
endfor
.
@verb __OBJECT__:"reinitialize" this none this xd __PROGRAMMER__
@program __OBJECT__:reinitialize
caller_perms().wizard || raise(E_PERM);
/* Reinitializes the package caches from the specified list of
* packages. If the install fails (due to currently uninstalled
* packages) it moves the package to the end of the list, continues,
* and retries.
*/
if (args)
objects = (length(args) == 1 && typeof(args[1]) == LIST) ? args[1] | args;
else
objects = this.packages;
endif
for object in (objects)
if (typeof(object) != OBJ)
raise(E_INVARG, tostr("Invalid argument: ", object), object);
endif
endfor
/* limit the iterations */
limit = length(objects) ^ 2;
packages_backup = this.packages;
requires_cache_backup = this.requires_cache;
provides_cache_backup = this.provides_cache;
this.packages = {};
this.requires_cache = [];
this.provides_cache = [];
while (objects && limit)
{object, @objects} = objects;
$suspend_if_necessary(0);
limit = limit - 1;
try
this:_install(object.identifier, object.version, object);
except (ANY)
objects = {@objects, object};
endtry
endwhile
if (objects)
this.packages = packages_backup;
this.requires_cache = requires_cache_backup;
this.provides_cache = provides_cache_backup;
failed = "";
for object in (objects)
failed = failed ? tostr(failed, ", ", object) | tostr(object);
endfor
raise(E_INVARG, tostr("Failed: ", failed), objects);
endif
.
@verb __OBJECT__:"_check_required_provides" this none this xd
@program __OBJECT__:_check_required_provides
{required} = args;
(caller != this) && raise(E_PERM);
for selector in (required)
object = this:_match(@selector, this.provides_cache)[1];
if (valid(object))
required = setremove(required, selector);
endif
endfor
return required;
.
@verb __OBJECT__:"_check_provided_requires" this none this xd __PROGRAMMER__
@program __OBJECT__:_check_provided_requires
{provided} = args;
(caller != this) && raise(E_PERM);
for package in (provided)
{identifier, version} = package;
object = `this.requires_cache[identifier][version][1] ! E_RANGE => $failed_match';
if (!valid(object))
provided = setremove(provided, package);
endif
endfor
return provided;
.
@verb __OBJECT__:"_generate_global_mapping_keyed_on_object" this none this xd __WIZARD__
@program __OBJECT__:_generate_global_mapping_keyed_on_object
(caller != this) && raise(E_PERM);
{specifiers} = args;
/*
* Take a list of package specifiers, ensure that the specified
* packages are present/installed, and generate a mapping between
* objects and identifiers based on the information in each
* package manifest.
*
* $nothing maps to "__nothing__", and is included for free.
*/
global = {};
for specifier in (specifiers)
this:_suspend_if_necessary();
{package, ?identifier, ?version} = this:_match(@specifier, this.provides_cache);
if ($failed_match == package)
raise(E_INVARG, tostr("Failed match: ", toliteral(specifier)));
elseif ($ambiguous_match == package)
raise(E_INVARG, tostr("Ambiguous match: ", toliteral(specifier)));
endif
for item in (`package.manifest ! E_PROPNF => {}')
{object, label} = item;
global = {@global, {object, tostr(label, "|", identifier)}};
endfor
endfor
global = {@global, {$nothing, "__nothing__"}};
return global;
.
@verb __OBJECT__:"_generate_global_mapping_keyed_on_identifier" this none this xd __WIZARD__
@program __OBJECT__:_generate_global_mapping_keyed_on_identifier
(caller != this) && raise(E_PERM);
{specifiers} = args;
/*
* Take a list of package specifiers, ensure that the specified
* packages are present/installed, and generate a mapping between
* identifiers and objects based on the information in each
* package manifest.
*
* "__nothing__" maps to $nothing, and is included for free.
*/
global = {};
for specifier in (specifiers)
this:_suspend_if_necessary();
{package, ?identifier, ?version} = this:_match(@specifier, this.provides_cache);
if ($failed_match == package)
raise(E_INVARG, tostr("Failed match: ", toliteral(specifier)));
elseif ($ambiguous_match == package)
raise(E_INVARG, tostr("Ambiguous match: ", toliteral(specifier)));
endif
for item in (`package.manifest ! E_PROPNF => {}')
{object, label} = item;
global = {@global, {tostr(label, "|", identifier), object}};
endfor
endfor
global = {@global, {"__nothing__", $nothing}};
return global;
.
@verb __OBJECT__:"_map" this none this xd __PROGRAMMER__
@program __OBJECT__:_map
{verb, LIST, @options} = args;
(caller != this) && raise(E_PERM);
for i in [1..length(LIST)]
this:_suspend_if_necessary();
LIST[i] = this:(verb)(LIST[i], @options);
endfor
return LIST;
.
@verb __OBJECT__:"_lookup" this none this x __PROGRAMMER__
@program __OBJECT__:_lookup
(caller != this) && raise(E_PERM);
{collection, index} = args;
if (typeof(collection) == MAP)
return collection[index];
else
for item in (collection)
if (item[1] == index)
return item[2];
endif
endfor
endif
return E_RANGE;
.
@verb __OBJECT__:"_lookup_by_object" this none this xd __PROGRAMMER__
@program __OBJECT__:_lookup_by_object
(caller != this) && raise(E_PERM);
{object, global, local, package, ?target = ""} = args;
/* Given an object look up the corresponding reference/label. */
if (typeof(object) == LIST)
return this:_map(verb, @args);
endif
if ((ret = this:_lookup(local, object)) != E_RANGE)
return ret;
elseif ((ret = this:_lookup(global, object)) != E_RANGE)
return ret;
elseif (`object.wizard ! E_INVIND')
return "__wizard__";
elseif (object == caller_perms())
return "__owner__";
elseif (object == package)
return "__package__";
endif
if (target)
raise(E_INVARG, tostr("Lookup failed for: ", object, " on ", target));
else
raise(E_INVARG, tostr("Lookup failed for: ", object));
endif
.
@verb __OBJECT__:"_lookup_by_label" this none this xd __PROGRAMMER__
@program __OBJECT__:_lookup_by_label
(caller != this) && raise(E_PERM);
{label, global, local, package} = args;
/* Given a reference/label look up the corresponding object. */
if (typeof(label) == LIST)
return this:_map(verb, @args);
endif
if ("__nothing__" == label)
return this:_lookup(global, "__nothing__");
elseif ("__wizard__" == label)
return caller_perms();
elseif ("__owner__" == label)
return caller_perms();
elseif ("__package__" == label)
return package;
elseif (r = match(label, "^%([a-z_][a-z0-9_]*%)%(|%([a-z][a-z0-9_]*%)%)?$"))
reference = label[r[3][1][1]..r[3][1][2]];
identifier = label[r[3][3][1]..r[3][3][2]];
if (reference && identifier)
if ((ret = this:_lookup(global, label)) != E_RANGE)
return ret;
endif
else
if ((ret = this:_lookup(local, label)) != E_RANGE)
return ret;
endif
endif
endif
raise(E_INVARG, tostr("Lookup failed for: ", toliteral(label)));
.
@verb __OBJECT__:"_is_ancestor" this none this xd __PROGRAMMER__
@program __OBJECT__:_is_ancestor
{subject, target, MAP} = args;
(caller != this) && raise(E_PERM);
keys = mapkeys(MAP);
stack = {target};
while (stack)
this:_suspend_if_necessary();
{target, @stack} = stack;
if (target in keys)
parents = `MAP[target]["Attributes"]["parents"]["Value"]["value"] ! E_RANGE => "__nothing__"';
if (typeof(parents) == LIST)
if (subject in parents)
return 1;
else
stack = {@parents, @stack};
endif
else
if (subject == parents)
return 1;
else
stack = {parents, @stack};
endif
endif
endif
endwhile
return 0;
.
@verb __OBJECT__:"_contents" this none this xd __PROGRAMMER__
@program __OBJECT__:_contents
{object} = args;
(caller != this) && raise(E_PERM);
objects = {};
if (valid(object))
stack = {object};
while (stack)
top = stack[1];
stack = {@top.contents, @stack[2..$]};
objects = {@objects, top};
endwhile
endif
return objects;
.
@verb __OBJECT__:"install" this none this xd __WIZARD__
@program __OBJECT__:install
caller_perms().wizard || raise(E_PERM);
`{package, @options} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: package, @options")';
if (options)
if (typeof(options[1]) != MAP)
{?identifier, ?version, ?options = []} = options;
else
options = options[1];
endif
endif
try
identifier;
version;
except (E_VARNF)
identifier = package.identifier;
version = package.version;
endtry
(!valid(package.location)) || raise(E_INVARG, "Not in $nothing");
(package in this.packages) && raise(E_INVARG, "Package is already installed");
provides = `package.provides ! E_PROPNF => {}';
provides = {{identifier, version}, @provides};
for p in (provides)
{id, v} = p;
valid(this:_match(id, v)[1]) && raise(E_INVARG, tostr("Package is already installed: identifier = ", id, ", version = ", v));
endfor
requires = `package.requires ! E_PROPNF => {}';
(requires = this:_check_required_provides(requires)) && raise(E_INVARG, tostr("Package requires: ", toliteral(requires)));
errors = [];
if (respond_to(package, "before_install"))
try
package:before_install();
except ex (ANY)
errors["before_install"] = ex;
endtry
endif
if (`options["follow-instructions"] ! ANY')
this:_do_instructions(identifier, version, package);
endif
this:_install(identifier, version, package);
if (respond_to(package, "after_install"))
try
package:after_install();
except ex (ANY)
errors["after_install"] = ex;
endtry
endif
return errors || 0;
.
@verb __OBJECT__:"uninstall" this none this xd __WIZARD__
@program __OBJECT__:uninstall
caller_perms().wizard || raise(E_PERM);
`{identifier, version, ?options = []} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: identifier, version, ?options")';
valid(object = this:_match(identifier, version)[1]) || raise(E_INVARG, tostr("Package is not installed: identifier = ", identifier, ", version = ", version));
provides = `object.provides ! E_PROPNF => {}';
provides = {{identifier, version}, @provides};
(provides = this:_check_provided_requires(provides)) && raise(E_INVARG, tostr("Package provides: ", toliteral(provides)));
errors = [];
if (respond_to(object, "before_uninstall"))
try
object:before_uninstall();
except ex (ANY)
errors["before_uninstall"] = ex;
endtry
endif
if (`options["follow-instructions"] ! ANY')
this:_undo_instructions(identifier, version, object);
endif
this:_uninstall(identifier, version, object);
if (respond_to(object, "after_uninstall"))
try
object:after_uninstall();
except ex (ANY)
errors["after_uninstall"] = ex;
endtry
endif
return errors || 0;
.
@verb __OBJECT__:"delete" this none this xd __WIZARD__
@program __OBJECT__:delete
args || raise(E_ARGS);
packages = this.packages;
set_task_perms(caller_perms());
if (((t = typeof(args[1])) == OBJ) || (t == ANON))
{object, ?options = []} = args;
identifier = version = "";
else
{identifier, version, ?options = []} = args;
object = this:_match(identifier, version)[1];
(object == $failed_match) && raise(E_INVARG, tostr("Package is not installed: identifier = ", identifier, ", version = ", version));
endif
(object == $lookup("__package__")) && raise(E_INVARG, tostr("Can't delete privileged package: identifier = ", object.identifier, ", version = ", object.version));
p = caller_perms();
while (valid(p))
(p == object) && raise(E_INVARG, tostr(caller_perms(), " is/is inside of ", object));
p = p.location;
endwhile
errors = [];
if (object in packages)
identifier = identifier || object.identifier;
version = version || object.version;
provides = {{identifier, version}};
(provides = this:_check_provided_requires(provides)) && raise(E_INVARG, tostr("Package provides: ", toliteral(provides)));
if (valid(object) && respond_to(object, "before_uninstall"))
try
object:before_uninstall();
except ex (ANY)
errors["before_uninstall"] = ex;
endtry
endif
if (`options["follow-instructions"] ! ANY')
this:_undo_instructions(identifier, version, object);
endif
this:_uninstall(identifier, version, object);
if (valid(object) && respond_to(object, "after_uninstall"))
try
object:after_uninstall();
except ex (ANY)
errors["after_uninstall"] = ex;
endtry
endif
endif
if (valid(object))
for item in (this:_contents(object))
`recycle(item) ! ANY => 0';
endfor
endif
return errors || 0;
.
@verb __OBJECT__:"_set_seconds_and_ticks" this none this xd __WIZARD__
@program __OBJECT__:_set_seconds_and_ticks
(caller != this) && raise(E_PERM);
bg_seconds = $server_options.bg_seconds;
bg_ticks = $server_options.bg_ticks;
$server_options.bg_seconds = 60 * 60;
$server_options.bg_ticks = (1024 * 1024) * 1024;
suspend(0);
return {bg_seconds, bg_ticks};
.
@verb __OBJECT__:"_reset_seconds_and_ticks" this none this xd __WIZARD__
@program __OBJECT__:_reset_seconds_and_ticks
(caller != this) && raise(E_PERM);
{args} = args;
{bg_seconds, bg_ticks} = args;
$server_options.bg_seconds = bg_seconds;
$server_options.bg_ticks = bg_ticks;
.
@verb __OBJECT__:"import" this none this xd __WIZARD__
@program __OBJECT__:import
{package} = args;
set_task_perms(caller_perms());
try
configuration = package["Configuration"];
version = configuration["version"];
top = configuration["top"];
objects = package["Objects"];
objects[top];
except ex (E_TYPE, E_RANGE)
raise(E_INVARG, "Incompatible package format");
endtry
(version in {"0.1", "0.2"}) || raise(E_INVARG, "Unsupported package version");
anonymous = (version == "0.2") ? configuration["anonymous"] | {};
requires = `objects[top]["Values"]["requires"]["Value"]["value"] ! E_RANGE => {}';
manifest = `objects[top]["Values"]["manifest"]["Value"]["value"] ! E_RANGE => {}';
relocate = `objects[top]["Values"]["relocate"]["Value"]["value"] ! E_RANGE => {}';
(missing = this:_check_required_provides(requires)) && raise(E_INVARG, tostr("Package requires: ", toliteral(missing)));
global = this:_generate_global_mapping_keyed_on_identifier(requires);
labels = {};
mapkeys_objects = mapkeys(objects);
for label in (manifest)
(label in mapkeys_objects) || raise(E_INVARG, tostr("Invalid value in `manifest': ", label));
labels = setadd(labels, label);
endfor
for label in (mapkeys_objects)
labels = setadd(labels, label);
endfor
unsorted = labels;
labels = {};
for label in (unsorted)
i = 1;
for target in (labels)
if (this:_is_ancestor(label, target, objects))
break;
endif
i = i + 1;
endfor
labels = {@labels[1..i - 1], label, @labels[i..$]};
endfor
success = 0;
try
local = {};
locations = [];
local = {@local, {top, top_object = create($nothing, top in anonymous)}};
for label in (setremove(labels, top))
this:_suspend_if_necessary();
local = {@local, {label, object = create($nothing, label in anonymous)}};
this:_move(object, top_object);
endfor
relocate_map = [];
for reference in (relocate)
this:_suspend_if_necessary();
if (r = match(reference, "^%([a-z_][a-z0-9_]*%)%.%([a-z_][a-z0-9_]*%)$"))
r1 = reference[r[3][1][1]..r[3][1][2]];
r2 = reference[r[3][2][1]..r[3][2][2]];
if (!(r2 in {"owner", "parents", "location"}))
(r1 in mapkeys(relocate_map)) || (relocate_map[r1] = {});
relocate_map[r1] = {@relocate_map[r1], r2};
endif
else
raise(E_INVARG, tostr("Invalid reference in `relocate': ", reference));
endif
endfor
for label in (labels)
this:_suspend_if_necessary();
object = this:_lookup(local, label);
definition = objects[label];
if (`definition["Values"]["owner"] ! E_RANGE' != E_RANGE)
if (caller_perms().wizard)
definition["Values"]["owner"]["Value"]["value"] = this:_lookup_by_label(definition["Values"]["owner"]["Value"]["value"], global, local, top_object);
else
definition["Values"] = mapdelete(definition["Values"], "owner");
endif
endif
if (`definition["Attributes"]["parents"] ! E_RANGE' != E_RANGE)
definition["Attributes"]["parents"]["Value"]["value"] = this:_lookup_by_label(definition["Attributes"]["parents"]["Value"]["value"], global, local, top_object);
endif
if (`definition["Values"]["location"] ! E_RANGE' != E_RANGE)
locations[label] = definition["Values"]["location"]["Value"]["value"];
definition["Values"] = mapdelete(definition["Values"], "location");
endif
if (`relocate_map[label] ! E_RANGE => 0')
for name in (relocate_map[label])
this:_suspend_if_necessary();
if (`definition["Values"][name]["Value"]["value"] ! E_RANGE => 0')
definition["Values"][name]["Value"]["value"] = this:_lookup_by_label(definition["Values"][name]["Value"]["value"], global, local, top_object);
else
for property in (definition["Properties"])
this:_suspend_if_necessary();
if (property["Property"]["name"] == name)
value = this:_lookup_by_label(property["Property"]["value"], global, local, top_object);
definition["Values"][name] = ["Value" -> ["value" -> value]];
endif
endfor
endif
endfor
endif
for _, value in (definition["Values"])
this:_suspend_if_necessary();
if (`definition["Values"][value]["Value"]["owner"] ! E_RANGE' != E_RANGE)
if (caller_perms().wizard)
definition["Values"][value]["Value"]["owner"] = this:_lookup_by_label(definition["Values"][value]["Value"]["owner"], global, local, top_object);
else
definition["Values"][value]["Value"] = mapdelete(definition["Values"][value]["Value"], "owner");
endif
endif
endfor
for index in [1..length(definition["Verbs"])]
this:_suspend_if_necessary();
if (`definition["Verbs"][index]["Verb"]["owner"] ! E_RANGE' != E_RANGE)
if (caller_perms().wizard)
definition["Verbs"][index]["Verb"]["owner"] = this:_lookup_by_label(definition["Verbs"][index]["Verb"]["owner"], global, local, top_object);
else
definition["Verbs"][index]["Verb"] = mapdelete(definition["Verbs"][index]["Verb"], "owner");
endif
endif
endfor
for index in [1..length(definition["Properties"])]
this:_suspend_if_necessary();
if (`definition["Properties"][index]["Property"]["owner"] ! E_RANGE' != E_RANGE)
if (caller_perms().wizard)
definition["Properties"][index]["Property"]["owner"] = this:_lookup_by_label(definition["Properties"][index]["Property"]["owner"], global, local, top_object);
else
definition["Properties"][index]["Property"] = mapdelete(definition["Properties"][index]["Property"], "owner");
endif
endif
endfor
definition = __SHAPES__:write_object(object, definition, ["verbs" -> ["do-not-version" -> 1, "do-not-stamp" -> 1]]);
objects[label] = definition;
package["Objects"] = objects;
endfor
for label in (labels)
this:_suspend_if_necessary();
if ("Error" in mapkeys(package["Objects"][label]))
raise("E_PACKAGE", "Error in package operation", package);
endif
endfor
for label in (labels)
this:_suspend_if_necessary();
if (label != top)
object = this:_lookup(local, label);
value = `locations[label] ! E_RANGE => "__package__"';
value = (value == "__package__") ? top_object | this:_lookup(local, value);
this:_move(object, value);
endif
endfor
for entry in [1..`length(top_object.manifest) ! E_PROPNF => 0']
this:_suspend_if_necessary();
top_object.manifest[entry] = {this:_lookup(local, top_object.manifest[entry]), top_object.manifest[entry]};
endfor
success = 1;
finally
success || this:delete(top_object);
endtry
return top_object;
.
@verb __OBJECT__:"export" this none this xd __WIZARD__
@program __OBJECT__:export
{package, ?options = []} = args;
set_task_perms(caller_perms());
strip = `options["strip"] ! E_RANGE => {}';
truncate = `options["truncate"] ! E_RANGE => {}';
`valid(package) ! E_TYPE => 0' || raise(E_INVARG, "Not a valid package");
(!valid(package.location)) || raise(E_INVARG, "Not a valid package: must not have a location");
requires = `package.requires ! E_PROPNF => {}';
manifest = `package.manifest ! E_PROPNF => {}';
relocate = `package.relocate ! E_PROPNF => {}';
objects = this:_contents(package);
for item in (manifest)
{object, label} = item;
`valid(object) ! E_TYPE' || raise(E_INVARG, tostr("Invalid object in manifest: ", label));
(caller_perms().wizard || (package.owner == object.owner)) || raise(E_PERM, tostr("Invalid object in manifest: ", label));
objects = setadd(objects, object);
endfor
definition = ["Configuration" -> ["Version" -> "0.2"], "Objects" -> []];
(missing = this:_check_required_provides(requires)) && raise(E_INVARG, tostr("Package requires: ", toliteral(missing)));
global = this:_generate_global_mapping_keyed_on_object(requires);
for key in (`mapkeys(options["global"]) ! E_RANGE => {}')
this:_suspend_if_necessary();
global = {@global, {key, options["global"][key]}};
endfor
local = {};
anonymous = {};
for object in (objects)
this:_suspend_if_necessary();
if ((t = typeof(object)) == OBJ)
label = tostr("__", toint(object), "__");
else
label = tostr("__", random(), "__");
endif
if (object == package)
label = "__package__";
endif
for entry in (manifest)
if (entry[1] == object)
label = entry[2];
break;
endif
endfor
local = {@local, {object, label}};
if (t == ANON)
anonymous = {@anonymous, label};
endif
definition["Objects"][label] = __SHAPES__:read_object(object, ["strip_clear_values" -> 1]);
if (object in strip)
values = [];
for name in ({"name", "owner", "location", "programmer", "wizard", "r", "w", "f", "a"})
values[name] = definition["Objects"][label]["Values"][name];
endfor
for index in [1..length(definition["Objects"][label]["Properties"])]
name = definition["Objects"][label]["Properties"][index]["property"]["name"];
values[name] = definition["Objects"][label]["Values"][name];
endfor
definition["Objects"][label]["Values"] = values;
endif
endfor
top = definition["Configuration"]["top"] = this:_lookup(local, package);
definition["Configuration"]["anonymous"] = anonymous;
labels = {};
for entry in (manifest)
this:_suspend_if_necessary();
labels = {@labels, entry[2]};
endfor
for object in (objects)
this:_suspend_if_necessary();
label = this:_lookup(local, object);
for _, v in (definition["Objects"][label]["Values"])
this:_suspend_if_necessary();
if (`"owner" in mapkeys(definition["Objects"][label]["Values"][v]["Value"]) ! E_RANGE')
definition["Objects"][label]["Values"][v]["Value"]["owner"] = this:_lookup_by_object(definition["Objects"][label]["Values"][v]["Value"]["owner"], global, local, package, label);
endif
endfor
for i in [1..length(definition["Objects"][label]["Properties"])]
this:_suspend_if_necessary();
definition["Objects"][label]["Properties"][i]["Property"]["owner"] = this:_lookup_by_object(definition["Objects"][label]["Properties"][i]["Property"]["owner"], global, local, package, label);
endfor
for i in [1..length(definition["Objects"][label]["Verbs"])]
this:_suspend_if_necessary();
definition["Objects"][label]["Verbs"][i]["Verb"]["owner"] = this:_lookup_by_object(definition["Objects"][label]["Verbs"][i]["Verb"]["owner"], global, local, package, label);
endfor
if ("owner" in mapkeys(definition["Objects"][label]["Values"]))
definition["Objects"][label]["Values"]["owner"]["Value"]["value"] = this:_lookup_by_object(definition["Objects"][label]["Values"]["owner"]["Value"]["value"], global, local, package, label);
endif
definition["Objects"][label]["Attributes"]["parents"]["Value"]["value"] = this:_lookup_by_object(definition["Objects"][label]["Attributes"]["parents"]["Value"]["value"], global, local, package, label);
definition["Objects"][label]["Values"]["location"]["Value"]["value"] = this:_lookup_by_object(definition["Objects"][label]["Values"]["location"]["Value"]["value"], global, local, package, label);
endfor
for reference in (relocate)
this:_suspend_if_necessary();
if (r = match(reference, "^%([a-z_][a-z0-9_]*%)%.%([a-z_][a-z0-9_]*%)$"))
r1 = reference[r[3][1][1]..r[3][1][2]];
r2 = reference[r[3][2][1]..r[3][2][2]];
if (r1 in labels)
if (((r2 != "parents") && (r2 != "location")) && (r2 != "owner"))
try
definition["Objects"][r1]["Values"][r2]["Value"]["value"];
except (E_RANGE)
raise(E_INVARG, tostr("Invalid reference in `relocate': \"", r2, "\" in \"", reference, "\""));
endtry
definition["Objects"][r1]["Values"][r2]["Value"]["value"] = this:_lookup_by_object(definition["Objects"][r1]["Values"][r2]["Value"]["value"], global, local, package, r1);
for property in [1..length(definition["Objects"][r1]["Properties"])]
this:_suspend_if_necessary();
if (definition["Objects"][r1]["Properties"][property]["Property"]["name"] == r2)
definition["Objects"][r1]["Properties"][property]["Property"]["value"] = definition["Objects"][r1]["Values"][r2]["Value"]["value"];
break property;
endif
endfor
endif
else
raise(E_INVARG, tostr("Invalid reference in `relocate': \"", r1, "\" in \"", reference, "\""));
endif
else
raise(E_INVARG, tostr("Invalid reference in `relocate': ", reference));
endif
endfor
for object in (truncate)
this:_suspend_if_necessary();
if (object in objects)
label = this:_lookup(local, object);
values = [];
for _, value in (definition["Objects"][label]["Values"])
this:_suspend_if_necessary();
if (value in {"name", "owner", "location", "programmer", "wizard", "r", "w", "f", "a"})
values[value] = definition["Objects"][label]["Values"][value];
endif
endfor
definition["Objects"][label]["Values"] = values;
endif
endfor
if (("manifest" in mapkeys(definition["Objects"][top]["Values"])) && (!definition["Objects"][top]["Values"]["manifest"]["Value"]["clear"]))
for entry in [1..length(definition["Objects"][top]["Values"]["manifest"]["Value"]["value"])]
this:_suspend_if_necessary();
definition["Objects"][top]["Values"]["manifest"]["Value"]["value"][entry] = definition["Objects"][top]["Values"]["manifest"]["Value"]["value"][entry][2];
endfor
endif
for property in [1..length(definition["Objects"][top]["Properties"])]
this:_suspend_if_necessary();
if (definition["Objects"][top]["Properties"][property]["Property"]["name"] == "manifest")
for entry in [1..length(definition["Objects"][top]["Properties"][property]["Property"]["value"])]
this:_suspend_if_necessary();
definition["Objects"][top]["Properties"][property]["Property"]["value"][entry] = definition["Objects"][top]["Properties"][property]["Property"]["value"][entry][2];
endfor
break;
endif
endfor
return definition;
.
@verb __OBJECT__:"_parse_url" this none this x __PROGRAMMER__
@program __OBJECT__:_parse_url
(caller != this) && raise(E_PERM);
{url} = args;
if ((r = match(url, "^http://%([^/:]+%)%(:[0-9]+%)?%(.*%)$")))
host = url[r[3][1][1]..r[3][1][2]];
port = toint(url[r[3][2][1] + 1..r[3][2][2]]) || 80;
path = url[r[3][3][1]..r[3][3][2]];
return {host, port, path};
else
return 0;
endif
.
@verb __OBJECT__:"_fetch" this none this xd __WIZARD__
@program __OBJECT__:_fetch
(caller != this) && raise(E_PERM);
{url} = args;
/* Attempt an HTTP connection; handle success, follow redirects and
* raise errors on everything else. Note: this verb is optimized for
* fetching package lists/packages, and takes a few short-cuts. In
* particular, it simply uses `strsub()' to encode/decode the MOO
* binary string representation of tilde ("~"), relying on the fact
* that packages _should not have_ any binary (non-printable)
* characters in them.
*/
while redirect (1)
try
connection = 0;
url = strsub(url, "~", "~7E");
if ((r = this:_parse_url(url)))
{host, port, path} = r;
else
raise(E_INVARG, "Bad URL");
endif
try
connection = open_network_connection(host, port);
except (E_INVARG)
raise(E_INVARG, "Open network connection failed");
endtry
set_connection_option(connection, "hold-input", 1);
set_connection_option(connection, "binary", 1);
notify(connection, tostr("GET ", path, " HTTP/1.1~0D~0A"));
notify(connection, (port != 80) ? tostr("Host: ", host, ":", port, "~0D~0A") | tostr("Host: ", host, "~0D~0A"));
notify(connection, "Accept: application/json~0D~0A");
notify(connection, "~0D~0A");
response = read_http("response", connection);
if (`reason = response["error"] ! E_RANGE')
raise(E_INVARG, tostr("Fetch failed: ", reason[2]));
elseif (!((status = response["status"]) in {200, 301, 302, 303, 307}))
raise(E_INVARG, tostr("Fetch failed: invalid HTTP status: ", status));
elseif (status / 100 == 3)
try
url = response["headers"]["location"];
continue redirect;
except (E_RANGE)
raise(E_INVARG, "Fetch failed: missing HTTP header: Location");
endtry
endif
json = strsub(response["body"], "~7E", "~");
try
return parse_json(json, "embedded-types");
except (E_RANGE, E_INVARG)
raise(E_INVARG, "Bad JSON");
endtry
finally
`boot_player(connection) ! ANY';
endtry
endwhile
.
@verb __OBJECT__:"fetch_index" this none this xd __PROGRAMMER__
@program __OBJECT__:fetch_index
caller_perms().wizard || raise(E_PERM);
args && raise(E_ARGS);
sources = this.archive_sources;
this:_notify(player, {"h2", [], "Updating index from archive sources"});
indexes = [];
for source in (sources)
try
this:_suspend_if_necessary();
indexes[source] = this:_fetch(source);
this:_notify(player, " ", source);
except ex (E_INVARG)
this:_notify(player, " ", source, " ... ", {"span", ["class" -> "error"], ex[2]});
endtry
endfor
this:_notify(player);
archived = [];
while (sources)
/* Fetch in reverse order. The packages in the archives in
* `archive_sources' will take precedence in order, first to last by
* source.
*/
source = sources[$];
sources = sources[^ .. ($ - 1)];
try
index = indexes[source];
except (E_RANGE)
/* there must have been a fetch error */
continue;
endtry
/* Handle both the old and new index formats. Look for "_links" at
* the top-level to distinguish the two. See the HAL JSON
* specification for details on the format.
*/
if (`index["_links"] ! E_RANGE')
for package in (index["_embedded"]["packages"])
this:_suspend_if_necessary();
mk = mapkeys(package);
if ("identifier" in mk && "version" in mk && "_links" in mk)
if (`href = package["_links"]["self"]["href"] ! E_RANGE')
if ((r = this:_parse_url(href)))
/* full URL -- use it as is */
else
r = this:_parse_url(source);
if (href[1] == "/") /* absolute URL */
href = tostr("http://", r[1], ":", r[2], href);
else /* relative URL */
if ((i = rindex(r[3], "/")))
href = tostr("http://", r[1], ":", r[2], r[3][1..i], href);
else
href = tostr("http://", r[1], ":", r[2], "/", href);
endif
endif
endif
identifier = package["identifier"];
version = package["version"];
requires = `package["requires"] ! E_RANGE => {}';
`archived[identifier] ! E_RANGE' || (archived[identifier] = []);
archived[identifier][version] = ["href" -> href, "requires" -> requires];
endif
endif
endfor
else
for package in (index["Packages"])
this:_suspend_if_necessary();
mk = mapkeys(package);
if ("identifier" in mk && "version" in mk && "link" in mk)
mk = mapkeys(package["link"]);
if ("uri" in mk && "rel" in mk && package["link"]["rel"] == "package")
/* old style is relative URLs */
uri = package["link"]["uri"];
r = this:_parse_url(source);
if ((i = rindex(r[3], "/")))
href = tostr("http://", r[1], ":", r[2], r[3][1..i], uri);
else
href = tostr("http://", r[1], ":", r[2], "/", uri);
endif
identifier = package["identifier"];
version = package["version"];
requires = `package["requires"] ! E_RANGE => {}';
`archived[identifier] ! E_RANGE' || (archived[identifier] = []);
archived[identifier][version] = ["href" -> href, "requires" -> requires];
endif
endif
endfor
endif
endwhile
this.archived = archived;
this.last_fetch_index = time();
return archived;
.
@verb __OBJECT__:"fetch_package_to_cache" this none this xd __PROGRAMMER__
@program __OBJECT__:fetch_package_to_cache
caller_perms().wizard || raise(E_PERM);
`{identifier, version} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: identifier, version")';
package = this:_fetch(this.archived[identifier][version]["href"]);
top = package["Configuration"]["top"];
requires = `package["Objects"][top]["Values"]["requires"]["Value"]["value"] ! E_RANGE => {}';
packages = this.cached;
`packages[identifier] ! E_RANGE' || (packages[identifier] = []);
packages[identifier][version] = ["definition" -> package, "requires" -> requires];
this.cached = packages;
return package;
.
@verb __OBJECT__:"import_package_from_archive" this none this xd __WIZARD__
@program __OBJECT__:import_package_from_archive
caller_perms().wizard || raise(E_PERM);
`{identifier, version, ?upgrade = 0} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: identifier, version, ?upgrade = 0")';
package = this:_fetch(this.archived[identifier][version]["href"]);
try
package = this:import(package);
except ex ("E_PACKAGE")
package = ex[3];
endtry
return package;
.
@verb __OBJECT__:"import_package_from_cache" this none this xd __WIZARD__
@program __OBJECT__:import_package_from_cache
caller_perms().wizard || raise(E_PERM);
`{identifier, version, ?upgrade = 0} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: identifier, version, ?upgrade = 0")';
package = this.cached[identifier][version]["definition"];
try
package = this:import(package);
except ex ("E_PACKAGE")
package = ex[3];
endtry
return package;
.
@verb __OBJECT__:"export_package_to_cache" this none this xd __WIZARD__
@program __OBJECT__:export_package_to_cache
caller_perms().wizard || raise(E_PERM);
`{identifier, version, ?force = 1} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: identifier, version, ?force = 1")';
package = this:_match(identifier, version)[1];
package = this:export(package);
top = package["Configuration"]["top"];
requires = `package["Objects"][top]["Values"]["requires"]["Value"]["value"] ! E_RANGE => {}';
packages = this.cached;
`packages[identifier] ! E_RANGE' || (packages[identifier] = []);
packages[identifier][version] = ["definition" -> package, "requires" -> requires];
this.cached = packages;
return package;
.
@verb __OBJECT__:"import_package_from_file" this none this xd __WIZARD__
@program __OBJECT__:import_package_from_file
caller_perms().wizard || raise(E_PERM);
`{pathname, ?options = []} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: pathname, ?options")';
fh = -1;
try
fh = file_open(pathname, "r-tn");
package = parse_json(file_readline(fh), "embedded-types");
finally
(fh > -1) && file_close(fh);
endtry
try
package = this:import(package);
except ex ("E_PACKAGE")
package = ex[3];
endtry
return package;
.
@verb __OBJECT__:"export_package_to_file" this none this xd __WIZARD__
@program __OBJECT__:export_package_to_file
caller_perms().wizard || raise(E_PERM);
`{identifier, version, pathname, ?options = []} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: identifier, version, pathname, ?options")';
package = this:_match(identifier, version)[1];
package = this:export(package, options);
fh = -1;
try
fh = file_open(pathname, "w-tn");
file_writeline(fh, generate_json(package, "embedded-types"));
finally
(fh > -1) && file_close(fh);
endtry
return package;
.
@verb __OBJECT__:"pretty_print_cached_package" this none this xd __WIZARD__
@program __OBJECT__:pretty_print_cached_package
caller_perms().wizard || raise(E_PERM);
`{identifier, version} = args ! E_ARGS => raise(E_ARGS, "Incorrect number of arguments: identifier, version")';
package = this.cached[identifier][version]["definition"];
json = generate_json(package, "embedded-types");
suspend(0);
while (len = length(json))
suspend(0);
len = len > 60000 ? 60000 | len;
line = json[1..len];
json[1..len] = "";
while (buffered_output_length(player))
suspend(0);
endwhile
notify(player, line);
endwhile
.
@verb __OBJECT__:"@install" any with this xd __WIZARD__
@program __OBJECT__:@install
if (callers() && caller_perms() != player)
raise(E_PERM);
endif
if (!player.wizard)
notify(player, "Only wizards can do that!");
return;
endif
if (r = match(dobjstr, "^ *%([_a-z0-9]+%) *%([0-9]+%.[0-9]+%.[0-9]+%)? *$"))
/* Modify the execution limits to give this (potentially)
* long-running task some breathing room, and to keep things
* atomic.
*/
seconds_and_ticks = 0;
try
seconds_and_ticks = this:_set_seconds_and_ticks();
identifier = dobjstr[r[3][1][1]..r[3][1][2]];
version = dobjstr[r[3][2][1]..r[3][2][2]];
if (`this.provides_cache[identifier] ! E_RANGE' != E_RANGE)
notify(player, tostr("Package \"", identifier, "\" is already installed."));
return;
elseif (`this.archived[identifier] ! E_RANGE' == E_RANGE && `this.cached[identifier] ! E_RANGE' == E_RANGE)
notify(player, tostr("Package \"", identifier, "\" doesn't exist in either the remote archive or local cache."));
return;
else
if (version)
if (`this.archived[identifier][version] ! E_RANGE' == E_RANGE && `this.cached[identifier][version] ! E_RANGE' == E_RANGE)
notify(player, tostr("Version \"", version, "\" of package \"", identifier, "\" doesn't exist in either the remote archive or local cache."));
return;
endif
else
cached = `mapkeys(this.cached[identifier]) ! E_RANGE => {}';
archived = `mapkeys(this.archived[identifier]) ! E_RANGE => {}';
versions = {@cached, @archived};
versions = this:_map("_parse_version", versions);
versions = this:_sort_versions(versions);
version = versions[1];
version = tostr(version[1], ".", version[2], ".", version[3]);
endif
endif
if (`this.cached[identifier][version] ! E_RANGE' != E_RANGE)
requires = `this.cached[identifier][version]["requires"] ! E_RANGE => {}';
else
requires = `this.archived[identifier][version]["requires"] ! E_RANGE => {}';
endif
if (requires && (requires = this:_check_required_provides(requires)))
notify(player, tostr("Version \"", version, "\" of package \"", identifier, "\" couldn't be installed because it requires:"));
sep = "";
line = " ";
for require in (requires)
{ispec, ?vspec = ""} = require;
if (vspec)
line = tostr(line, sep, ispec, " ", vspec);
else
line = tostr(line, sep, ispec);
endif
sep = ", ";
endfor
notify(player, line);
return;
endif
if (`this.cached[identifier][version] ! E_RANGE' != E_RANGE)
notify(player, tostr("Installing version \"", version, "\" of package \"", identifier, "\" from the local cache..."));
package = this:import_package_from_cache(identifier, version);
else
notify(player, tostr("Installing version \"", version, "\" of package \"", identifier, "\" from the remote archive..."));
package = this:import_package_from_archive(identifier, version);
endif
if ((t = typeof(package)) == OBJ || t == ANON)
this:install(package, ["follow-instructions" -> 1]);
else
notify(player, tostr("Version \"", version, "\" of package \"", identifier, "\" couldn't be installed (raw package below)."));
notify(player, toliteral(package));
return;
endif
notify(player, tostr("Version \"", version, "\" of package \"", identifier, "\" (", package.name, ") was successfully installed as ", package, "."));
finally
this:_reset_seconds_and_ticks(seconds_and_ticks);
endtry
else
notify(player, tostr("Correct usage is: @install <package identifier> <package version> with $composed"));
notify(player, tostr(" for example: @install foobar 1.2.3 with $composed"));
endif
.
@verb __OBJECT__:"@uninstall" any with this xd __WIZARD__
@program __OBJECT__:@uninstall
if (callers() && caller_perms() != player)
raise(E_PERM);
endif
if (!player.wizard)
notify(player, "Only wizards can do that!");
return;
endif
if (r = match(dobjstr, "^ *%([_a-z0-9]+%) *%([0-9]+%.[0-9]+%.[0-9]+%) *$"))
identifier = dobjstr[r[3][1][1]..r[3][1][2]];
version = dobjstr[r[3][2][1]..r[3][2][2]];
if (`this.provides_cache[identifier][version] ! E_RANGE' == E_RANGE)
notify(player, tostr("Version \"", version, "\" of package \"", identifier, "\" is not installed."));
return;
endif
package = this.provides_cache[identifier][version][1];
this:delete(package, ["follow-instructions" -> 1]);
notify(player, tostr("Version \"", version, "\" of package \"", identifier, "\" was successfully uninstalled."));
else
notify(player, tostr("Correct usage is: @uninstall <package identifier> <package version> with $composed"));
notify(player, tostr(" for example: @uninstall foobar 1.2.3 with $composed"));
endif
.
@verb __OBJECT__:"@list*-packages" any with this xd __WIZARD__
@program __OBJECT__:@list-packages
if (callers() && caller_perms() != player)
raise(E_PERM);
endif
if (!player.wizard)
this:_notify(player, "Only wizards can do that!");
return;
endif
if (dobjstr && dobjstr != "packages")
this:_notify(player, "I couldn't list that.");
this:_notify(player, "Usage: ", {"code", [], verb, " packages with $composed"});
this:_notify(player, " ", {"code", [], verb, " with $composed"});
return;
endif
this:fetch_index();
cached = this.cached;
archived = this.archived;
this:_notify(player, {"h2", [], "Installed packages"});
for _, identifier in (this.provides_cache)
for _, version in (this.provides_cache[identifier])
this:_suspend_if_necessary();
if (`version in mapkeys(cached[identifier]) ! E_RANGE' || `version in mapkeys(archived[identifier]) ! E_RANGE')
this:_notify(player, {"span", [], " ", identifier, ", ", version, " (", objnum = this.provides_cache[identifier][version][1], ") ", objnum.name});
else
this:_notify(player, {"span", ["class" -> "information"], " ! ", identifier, ", ", version, " (", objnum = this.provides_cache[identifier][version][1], ") ", objnum.name});
endif
endfor
endfor
this:_notify(player);
this:_notify(player, {"h2", [], "Packages available from cache"});
for _, identifier in (this.cached)
this:_suspend_if_necessary();
versions = "";
for _, version in (this.cached[identifier])
versions = versions + ", " + version;
endfor
this:_notify(player, " ", identifier, versions);
endfor
this:_notify(player);
this:_notify(player, {"h2", [], "Packages available from archives"});
for _, identifier in (this.archived)
this:_suspend_if_necessary();
versions = "";
for _, version in (this.archived[identifier])
versions = versions + ", " + version;
endfor
this:_notify(player, " ", identifier, versions);
endfor
this:_notify(player);
this:_notify(player, "(done)");
.
@verb __OBJECT__:"_do_instructions" this none this xd __WIZARD__
@program __OBJECT__:_do_instructions
(caller != this) && raise(E_PERM);
{identifier, version, package} = args;
manifest = `package.manifest ! E_PROPNF => {}';
instructions = `package.instructions ! E_PROPNF => {}';
if ("install-dictionary" in instructions)
for item in (manifest)
{object, label} = item;
if ("dictionary" == label)
notify(player, tostr("Adding package dictionary (", object.name, ") to parents of $system..."));
parents = {@parents($system), object};
chparents($system, parents);
break;
endif
endfor
endif
if ("install-namespace" in instructions)
for item in (manifest)
{object, label} = item;
if ("dictionary" == label)
notify(player, tostr("Adding namespace (", identifier, ") as a property on $system..."));
add_property($system, identifier, object, {package, "r"});
break;
endif
endfor
endif
for instruction in (instructions)
if (typeof(`$sysobj ! ANY') == OBJ && (r = match(instruction, "^install-%([_a-z0-9]+%)-on-legacy-core$")))
property = instruction[r[3][1][1]..r[3][1][2]];
for item in (manifest)
{object, label} = item;
if (property == label)
notify(player, tostr("Adding property (", label, ") to $sysobj..."));
add_property($sysobj, label, object, {package, "r"});
endif
endfor
endif
endfor
.
@verb __OBJECT__:"_undo_instructions" this none this xd __WIZARD__
@program __OBJECT__:_undo_instructions
(caller != this) && raise(E_PERM);
{identifier, version, package} = args;
manifest = `package.manifest ! E_PROPNF => {}';
instructions = `package.instructions ! E_PROPNF => {}';
if ("install-dictionary" in instructions)
for item in (manifest)
{object, label} = item;
if ("dictionary" == label && object in parents($system))
notify(player, tostr("Removing package dictionary (", object.name, ") from parents of $system..."));
parents = setremove(parents($system), object);
chparents($system, parents);
break;
endif
endfor
endif
if ("install-namespace" in instructions)
for item in (manifest)
{object, label} = item;
if ("dictionary" == label && identifier in properties($system))
notify(player, tostr("Removing namespace (", identifier, ") as a property on $system..."));
delete_property($system, identifier);
break;
endif
endfor
endif
for instruction in (instructions)
if (typeof(`$sysobj ! ANY') == OBJ && (r = match(instruction, "^install-%([_a-z0-9]+%)-on-legacy-core$")))
property = instruction[r[3][1][1]..r[3][1][2]];
for item in (manifest)
{object, label} = item;
if (property == label && property in properties($sysobj))
notify(player, tostr("Removing property (", label, ") from $sysobj..."));
delete_property($sysobj, label);
endif
endfor
endif
endfor
.
"***finished***
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment