#!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; my %externalized_functions; my @data_types; my @script_args; sub meta::define_form { my ($namespace, $delegate) = @_; push @data_types, $namespace; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'meta', sub { my ($name, $value) = @_; eval $value; carp $@ if $@; }; meta::meta('datatypes::bootstrap', <<'__guYWiOv4zBmdrlI3k3sW7f/q/xsX38Xvzz0dwwLCIRM'); meta::define_form 'bootstrap', sub {}; __guYWiOv4zBmdrlI3k3sW7f/q/xsX38Xvzz0dwwLCIRM meta::meta('datatypes::data', <<'__BbqBTgoNfUhDgUoDmty0D7Okp/jdQ8n6XWTCIcsKc68'); meta::define_form 'data', sub { my ($name, undef) = @_; $externalized_functions{$name} = "data::$name"; *{$name} = sub { $data{"data::$name"} = $_[1] || join '', if @_ > 0 && @_[0] == '='; $data{"data::$name"}; }; }; __BbqBTgoNfUhDgUoDmty0D7Okp/jdQ8n6XWTCIcsKc68 meta::meta('datatypes::function', <<'__XSIHGGHv0Sh0JBj9KIrP/OzuuB2epyvn9pgtZyWE6t0'); meta::define_form 'function', sub { my ($name, $value) = @_; $externalized_functions{$name} = "function::$name"; *{$name} = eval "sub {\n$value\n}"; carp $@ if $@; }; __XSIHGGHv0Sh0JBj9KIrP/OzuuB2epyvn9pgtZyWE6t0 meta::meta('datatypes::internal_function', <<'__heBxmlI7O84FgR+9+ULeiCTWJ4hqd079Z02rZnl9Ong'); meta::define_form 'internal_function', sub { my ($name, $value) = @_; *{$name} = eval "sub {\n$value\n}"; carp $@ if $@; }; __heBxmlI7O84FgR+9+ULeiCTWJ4hqd079Z02rZnl9Ong meta::meta('internal::runtime', <<'__Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA'); meta::define_form 'internal', \&meta::meta::implementation; __Nd6Dp1A6nL7yAGeoRfeZETeaW8vnPN8HI9Diqo66vDA meta::bootstrap('initialization', <<'__bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::SHA 'sha256_base64'; my %data; my %externalized_functions; my @data_types; my @script_args; sub meta::define_form { my ($namespace, $delegate) = @_; push @data_types, $namespace; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; chomp $value; $data{"${namespace}::$name"} = $value; $delegate->($name, $value); }; } meta::define_form 'meta', sub { my ($name, $value) = @_; eval $value; carp $@ if $@; }; __bzFASokNzruPfsSIlwZC8OG4NFNI9EfmMYeL/oWGaDs meta::bootstrap('pod', <<'__0h2CBA2cqa4qd6nox9dul6Jn9hcJFHw3uPdC89Xim7o'); =head1 NAME object - Stateful file-based object =head1 SYNOPSYS object [options] action [arguments...] object shell =head1 DESCRIPTION Stateful objects preserve their state between executions by rewriting themselves. Each time the script exits it replaces its contents with its new state. Thus state management, for user-writable scripts, is completely transparent. An object rewrites itself only if its state has changed. This may seem like a dangerous operation, but some checks are put into place to ensure that it goes smoothly. First, the object is initially written to a separate file. Next, that file is executed and asked to provide a hashsum of its contents. The original object is rewritten only if that hashsum is correct. This ensures that the replacement object is functional and has the right data. Currently the only known way to lose your data is to edit the serialization-related functions in such a way that they no longer function. However, this is not something most people will normally do. In the future there may be a locking mechanism to prevent unintentional edits of these attributes. =cut __0h2CBA2cqa4qd6nox9dul6Jn9hcJFHw3uPdC89Xim7o meta::data('default-action', <<'__zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc'); shell __zmNcTqv/Xk9W26j7HjnKI1UwqitrGFM+7xrzhiAWxXc meta::data('name', <<'__m24nLRKoWqV1P9RNQt50QGqKHHWYTOHLBqmalFJd6Ag'); canonical-object __m24nLRKoWqV1P9RNQt50QGqKHHWYTOHLBqmalFJd6Ag meta::function('cat', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk'); my ($name) = @_; $data{$name}; __h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk meta::function('clone', <<'__qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY'); for (@_) { if ($_) { eval { file::write($_, serialize(), noclobber => 1); chmod(0700, $_); print "File $_ cloned successfully.\n"; }; print "$@\n" if $@; } } __qP6xPZE75s9g0XJIiC6FGw0vnj2j0glUzsAHxyA3lvY meta::function('cp', <<'__yn1SQkcEk6o+gnuCy3QGVFtQb2piaCoUdJPGUkLjpD4'); my ($from, $to) = @_; $data{$to} = $data{$from} if $data{$from}; messages::error("No such attribute $from") unless $data{$from}; $data{$from}; __yn1SQkcEk6o+gnuCy3QGVFtQb2piaCoUdJPGUkLjpD4 meta::function('create', <<'__4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY'); my ($name, $value) = @_; messages::error("Attribute $name already exists.") if grep {$_ eq $name} keys %data; associate($name, $value || join('', ) || "\n"); __4e/yca7FeKtJK0U61l9uCtsCPTiznglZlwh6U3iRLvY meta::function('edit', <<'__35YrIzrL8rUWpvFrOQY4NIL0KybZ8OEndLy3JkIIEfQ'); my ($name, %options) = @_; messages::error("Attribute $name does not exist.") unless grep {$_ eq $name} keys %data; associate($name, invoke_editor_on($data{$name} || "# Attribute $name", %options), execute => $name !~ /^internal::/ && $name !~ /^bootstrap::/); delete $data{$name} if length($data{$name}) == 0; __35YrIzrL8rUWpvFrOQY4NIL0KybZ8OEndLy3JkIIEfQ meta::function('exists', <<'__bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk'); my $name = shift; grep {$_ eq $name} keys %data; __bxU1sDtIh3+P1x0HuuY0f7sKHr9qNZUEl64m2fvwmDk meta::function('import', <<'__oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA'); my ($name) = @_; associate($name, join('', )); __oK2Kj5RYHcEUK0Iyiqu8w7zipbg+QNF4VO4hm7BkUNA meta::function('ls', <<'__M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg'); join("\n", sort keys %externalized_functions); __M3wGXSw8/xm3RiNq0uLWke1dHm2OWQbvJpHkngdPafg meta::function('ls-a', <<'__6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18'); join("\n", map {" $_"} sort keys %data) . "\n"; __6jKXRDXpIkzIOkcLtB2FOSTuZxqjBLyLZsF1vEmVn18 meta::function('mv', <<'__PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y'); my ($from, $to) = @_; messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data; $data{$to} = $data{$from}; delete $data{$from}; __PY7iwIY+6QtPN4V5hV4MOImRJVAKDkMmEKtkN34cv5Y meta::function('reload', <<'__GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM'); execute($_) for (grep {! (/^internal::/ || /^bootstrap::/)} keys %data); __GwQjnnfuj0xQlervDJ9EVWzdmdz+XL3Gq0i9rdejvzM meta::function('rm', <<'__7BVECTVo/mcT5+edC70WPc6S1xCbzAeyUCfCjkKWlww'); for my $to_be_deleted (@_) { messages::warning("$to_be_deleted does not exist") unless grep {$_ eq $to_be_deleted} keys %data; } delete @data{@_}; __7BVECTVo/mcT5+edC70WPc6S1xCbzAeyUCfCjkKWlww meta::function('save', <<'__HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto'); my $serialized_data = serialize(); my $final_state = state(); my (undef, $temporary_filename) = tempfile("$0." . 'X' x 32, OPEN => 0); file::write($temporary_filename, $serialized_data); chmod 0700, $temporary_filename; my $observed_state = `perl $temporary_filename state`; chomp $observed_state; if ($observed_state ne $final_state) { messages::error("The state of this object ($final_state) is inconsistent with the state of $temporary_filename ($observed_state).\n" . "$0 has not been updated."); } else { file::write($0, $serialized_data); unlink $temporary_filename; } __HuNR2A6/zt/GGZDRiR1x82a4nBxpAlIR1QGc4kUySto meta::function('serialize', <<'__KGiI48MlyG6RAVW5QYRK8y97y8tx+jeAwPlY5eDtMTw'); my @keys_without_internals = grep(!/^internal::/, sort keys %data); join "\n", $data{'bootstrap::initialization'}, (grep {$_} (map {serialize::single(@_)} grep(/^meta::/, @keys_without_internals), grep(!/^meta::/, @keys_without_internals), grep(/^internal::/, sort keys %data))), "__END__"; __KGiI48MlyG6RAVW5QYRK8y97y8tx+jeAwPlY5eDtMTw meta::function('shell', <<'__1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4'); use Term::ReadLine; my $term = new Term::ReadLine "$0 shell"; $term->ornaments(0); my $prompt = name() . '$ '; my $OUT = $term->OUT || \*STDOUT; while (defined ($_ = $term->readline($prompt))) { my $command_line = $_; my @args = split /\s+/; my $function_name = shift @args; return if $function_name eq 'exit'; if ($externalized_functions{$function_name}) { my $result = eval {&{$function_name}(@args)}; messages::warning($@) if $@; chomp $result; print $OUT $result, "\n" unless $@; } else { messages::warning("Command not found: $function_name"); } $term->addhistory($command_line) if $command_line; $prompt = name() . '$ '; } __1pX0YiaO1Jx8AIJ3/w6tbrNGcmnuuZfIo6840aq4bU4 meta::function('size', <<'__lDGr6yVnDwcDWLkJH16MNukltjG2ypBSk/ktYb80h80'); length(serialize()); __lDGr6yVnDwcDWLkJH16MNukltjG2ypBSk/ktYb80h80 meta::function('snapshot', <<'__qjqsCy4CTt88dIi7IWM+Varpb3GcHsYrFTxW7EwpLW0'); my ($name) = @_; file::write(my $finalname = state_based_filename($name), serialize(), noclobber => 1); chmod 0700, $finalname; __qjqsCy4CTt88dIi7IWM+Varpb3GcHsYrFTxW7EwpLW0 meta::function('state', <<'__1S8nzRSMoxJU/VEv2rx/NrAt1iRgXQ9ugxjUP3IFunI'); sha256_base64 serialize(); __1S8nzRSMoxJU/VEv2rx/NrAt1iRgXQ9ugxjUP3IFunI meta::function('usage', <<'__oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I'); <<"EOD" . join ' ', split /\n/, ls (); Usage: $0 [options] action [arguments] Defined actions: EOD __oHVev4RtZlF/82SSE87y4Bf7ran2afn/HDtukOQBf9I meta::internal_function('associate', <<'__D8BKmEFp/adiPPqPnXyMOzlsBMCmuZi62UpJWdoFg/0'); my ($name, $value, %options) = @_; my $namespace = namespace($name); messages::error("Namespace $namespace does not exist") unless grep {$_ eq $namespace} @data_types; $data{$name} = $value; execute($name) if $options{'execute'}; __D8BKmEFp/adiPPqPnXyMOzlsBMCmuZi62UpJWdoFg/0 meta::internal_function('basename', <<'__T4JEqOUYjMzssdVwV/rdgAhvr0Vz9TQUo0noTdeBLxw'); my ($name) = @_; $name =~ s/^[^:]*:://; $name; __T4JEqOUYjMzssdVwV/rdgAhvr0Vz9TQUo0noTdeBLxw meta::internal_function('execute', <<'__FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE'); my ($name, %options) = @_; my $namespace = namespace($name); eval {&{"meta::$namespace"}(basename($name), retrieve($name))}; carp $@ if $@ && $options{'carp'}; __FfzmdPKSa4vnT4WNSN3uCxnwrUFKfkQbS6auoIa/SgE meta::internal_function('file::read', <<'__ZxBqZsMZZRuLMQp8Sy//ZsoAvriDebjYLGAX7p7AxXg'); my $name = shift; open my($handle), "<", $name; my $result = join "", <$handle>; close $handle; $result; __ZxBqZsMZZRuLMQp8Sy//ZsoAvriDebjYLGAX7p7AxXg meta::internal_function('file::write', <<'__+NhpMabvNL+hHZaTZwBoFx2IFa79cjOZwGxEXX+xG0o'); my ($name, $contents, %options) = @_; die "Choosing not to overwrite file $name" if $options{'noclobber'} && -f $name; open my($handle), ">", $name or die "Can't open $name for writing"; print $handle $contents; close $handle; __+NhpMabvNL+hHZaTZwBoFx2IFa79cjOZwGxEXX+xG0o meta::internal_function('invoke_editor_on', <<'__4Qkh4HQjoCLvAQ83W1ClaVM5dkrhv2YNMxIHO18k1cA'); my ($data, %options) = @_; my $content_hash = sha256_base64($data); my $editor = $options{'editor'} || $ENV{'VISUAL'} || $ENV{'EDITOR'} || messages::error('Either the $VISUAL or $EDITOR environment variable should be set to a valid editor.'); my $options = $options{'options'} || $ENV{'VISUAL_OPTS'} || $ENV{'EDITOR_OPTS'} || ''; my $extension = $options{'extension'} || ''; my (undef, $filename) = tempfile("$0." . ("X" x 32) . $extension, OPEN => 0); file::write($filename, $data); system("$editor $options \"$filename\""); my $result = file::read($filename); unlink $filename; $result; __4Qkh4HQjoCLvAQ83W1ClaVM5dkrhv2YNMxIHO18k1cA meta::internal_function('messages::error', <<'__200qXouilOAQNa4NkmIj6l+Rvb49Jpy8yxvIX29NcK4'); my ($message) = @_; die "$message\n"; __200qXouilOAQNa4NkmIj6l+Rvb49Jpy8yxvIX29NcK4 meta::internal_function('messages::warning', <<'__DeU/1Klulk/y4fO+wtKt+liOmUKwCEYKM8BvtlXYXBc'); my ($message) = @_; print "$message\n"; __DeU/1Klulk/y4fO+wtKt+liOmUKwCEYKM8BvtlXYXBc meta::internal_function('namespace', <<'__D7UfKyyYZ1slZZyaS28hIt8a68jkI3ELBaddROXOHug'); my ($name) = @_; $name =~ s/::.*$//; $name; __D7UfKyyYZ1slZZyaS28hIt8a68jkI3ELBaddROXOHug meta::internal_function('retrieve', <<'__h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk'); my ($name) = @_; $data{$name}; __h2PeSpk/pPmrzLRTTofdLTbhj06IWUw5WWke6ggUsdk meta::internal_function('serialize::single', <<'__lDBHaXpbrfER2envI2Ipy77IcdjUnlZou+rggaxsAWE'); my $name = shift || $_; my $contents = $data{$name}; my $delimiter = "__" . sha256_base64 $contents; my $meta_function_name = "meta::" . namespace($name); my $invocation_name = basename $name; "$meta_function_name('$invocation_name', <<'$delimiter');\n$contents\n$delimiter\n"; __lDBHaXpbrfER2envI2Ipy77IcdjUnlZou+rggaxsAWE meta::internal_function('state_based_filename', <<'__zNSrihAkMKJG5spRYgcFdoNArFKig1u12gIp6gJ8pZw'); my ($name) = @_; my $noise = $name || state(); $noise =~ s/\//-/g; "$0.$noise"; __zNSrihAkMKJG5spRYgcFdoNArFKig1u12gIp6gJ8pZw meta::internal('runtime', <<'__YPmIzwZkTg8URmPfjiwGRG4VDUF2ZCJqTEz+gjETYLQ'); my $initial_state = sha256_base64 serialize(); push @script_args, shift @ARGV while @ARGV && $ARGV[0] =~ /^-/; my $default_action = retrieve('data::default-action'); chomp $default_action; my $function_name = shift(@ARGV) || $default_action || 'usage'; $function_name = 'usage' unless $externalized_functions{$function_name}; my $result = &{$function_name}(@ARGV); chomp $result; print "$result\n" if $result; END { my $serialized_data = serialize(); my $final_state = sha256_base64 $serialized_data; save() unless $initial_state eq $final_state; } __YPmIzwZkTg8URmPfjiwGRG4VDUF2ZCJqTEz+gjETYLQ __END__