#!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::MD5 'md5_hex'; my %data; sub meta::define_form { my ($namespace, $delegate) = @_; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; $data{"${namespace}::${name}"} = $value; $delegate->($name, $value); }; } meta::define_form 'meta', sub { my ($name, $value) = @_; eval $value; carp $@ if $@; }; meta::meta('datatypes::bootstrap', <<'__b4109527493c00f6f20793eedd08ceb9'); meta::define_form 'bootstrap', sub {}; __b4109527493c00f6f20793eedd08ceb9 meta::meta('datatypes::data', <<'__428cabdae6955123a0e16328b2bbcfea'); meta::define_form 'data', sub { my ($name, $value) = @_; *{$name} = sub {print $value}; }; __428cabdae6955123a0e16328b2bbcfea meta::meta('datatypes::function', <<'__7f5dd8d7cd5b973823c50e677bc78216'); meta::define_form 'function', sub { my ($name, $value) = @_; *{$name} = eval "sub {$value}"; carp $@ if $@; }; __7f5dd8d7cd5b973823c50e677bc78216 meta::meta('internal::runtime', <<'__78031ac14943dc121f0471eda34370a6'); meta::define_form 'internal', \&meta::meta::implementation; __78031ac14943dc121f0471eda34370a6 meta::bootstrap('initialization', <<'__77ccbc556a1b8cc2bda5514aaf273676'); #!/usr/bin/perl use File::Temp 'tempfile'; use Carp 'carp'; use Digest::MD5 'md5_hex'; my %data; sub meta::define_form { my ($namespace, $delegate) = @_; *{"meta::${namespace}::implementation"} = $delegate; *{"meta::$namespace"} = sub { my ($name, $value) = @_; $data{"${namespace}::${name}"} = $value; $delegate->($name, $value); }; } meta::define_form 'meta', sub { my ($name, $value) = @_; eval $value; carp $@ if $@; }; __77ccbc556a1b8cc2bda5514aaf273676 meta::bootstrap('pod', <<'__89ed580add0efb12ac02b68703b31683'); =head1 NAME object - Stateful file-based object =head1 SYNOPSYS object [options] action [arguments...] object help::usage =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. =cut __89ed580add0efb12ac02b68703b31683 meta::function('file::read', <<'__c0308fd18332c7a715672d799529c0c2'); my $name = shift; open my($handle), "<", $name; my $result = join "", <$handle>; close $handle; $result; __c0308fd18332c7a715672d799529c0c2 meta::function('file::write', <<'__71b4a7aa5e93241ea07e8af462c77659'); my ($name, $contents) = @_; open my($handle), ">", $name or die "Can't open $name for writing"; print $handle $contents; close $handle; __71b4a7aa5e93241ea07e8af462c77659 meta::function('functions::edit', <<'__ea5d29bd19f7de210f38bd7da80d930c'); my $name = shift; my $content_hash = md5_hex($name . $data{$name}); my $editor = $ENV{'VISUAL'} || $ENV{'EDITOR'} || messages::error('Either the $VISUAL or $EDITOR environment variable should be set to a valid editor.'); my $namespace = ((split /::/, $name)[0]); my (undef, $filename) = tempfile("X" x 32, OPEN => 0); $data{$name} or $data{$name} = "# New attribute '$name'\n"; file::write($filename, $data{$name}); system("$editor \"$filename\""); if (-f $filename) { $data{$name} = file::read($filename); $data{$name} .= "\n" unless $data{$name} =~ /\n$/; unlink $filename; delete $data{$name} if length($data{$name}) == 0; } else { messages::warning("The temporary file used for editing no longer exists; $name has not been updated."); } __ea5d29bd19f7de210f38bd7da80d930c meta::function('functions::exists', <<'__929f454827ad3588fc1efa95f768cb12'); my $name = shift; if (grep /$name/, keys %data) { print "$name is a defined attribute.\n"; } else { print "$name is not defined.\n"; } __929f454827ad3588fc1efa95f768cb12 meta::function('functions::md5', <<'__0e1d4cafb9d662a98b1ec4a865c0ebed'); print md5_hex serialize(); __0e1d4cafb9d662a98b1ec4a865c0ebed meta::function('functions::mv', <<'__32d41be6d86846aba9fb57e92a9f69c1'); my ($from, $to) = @_; messages::error("The '$from' attribute does not exist.") unless grep $from, keys %data; $data{$to} = $data{$from}; delete $data{$from}; __32d41be6d86846aba9fb57e92a9f69c1 meta::function('functions::rm', <<'__e016d657842380be2387f361a90a24df'); my $name = shift; delete $data{$name}; __e016d657842380be2387f361a90a24df meta::function('help::ls', <<'__d13d1c1d60925eddf9c6a536b0f5a3b1'); print join("\n", map {s/^function:://; " $_"} grep /^function::/, sort keys %data) . "\n"; __d13d1c1d60925eddf9c6a536b0f5a3b1 meta::function('help::ls-a', <<'__fad7c89642d52f4e3bbbe016724d7aad'); print join "\n", map {" $_"} sort keys %data; print "\n"; __fad7c89642d52f4e3bbbe016724d7aad meta::function('help::usage', <<'__06d47adb0f1da5a85641dc37e5d8d58a'); print <<"EOD"; Usage: $0 [options] action [arguments] Defined actions: EOD help::ls(); __06d47adb0f1da5a85641dc37e5d8d58a meta::function('messages::error', <<'__e25c6104ad29725892e0540803adb254'); my $message = shift; print STDERR $message, "\n"; exit 1; __e25c6104ad29725892e0540803adb254 meta::function('messages::warning', <<'__bca9e541c182a26a59a3c9ab8d52cc38'); my $message = shift; print STDERR $message, "\n"; __bca9e541c182a26a59a3c9ab8d52cc38 meta::function('serialize', <<'__83ca9dbe0417fcd0d6df1ac876e3bcc2'); 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__"; __83ca9dbe0417fcd0d6df1ac876e3bcc2 meta::function('serialize::single', <<'__db61061746e4132e9995ba98f087bf5c'); my $name = shift || $_; my $contents = $data{$name}; my $delimiter = "__" . md5_hex $contents; my @function_name_bits = split /::/, $name; my $meta_function_name = "meta::" . shift @function_name_bits; my $invocation_name = join "::", @function_name_bits; "$meta_function_name('$invocation_name', <<'$delimiter');\n$contents$delimiter\n"; __db61061746e4132e9995ba98f087bf5c meta::internal('runtime', <<'__73e6199477c248d6085fac3ee0ff91c6'); my $initial_state = md5_hex serialize(); my @script_args = (); push @script_args, shift @ARGV while @ARGV && $ARGV[0] =~ /^-/; &{shift(@ARGV) || 'help::usage'}(@ARGV[1 .. @ARGV]); END { my $serialized_data = serialize(); my $final_state = md5_hex $serialized_data; if ($initial_state ne $final_state) { my (undef, $temporary_filename) = tempfile('X' x 32, OPEN => 0); file::write($temporary_filename, $serialized_data); chmod 0700, $temporary_filename; my $observed_state = `perl $temporary_filename functions::md5`; 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; } } } __73e6199477c248d6085fac3ee0ff91c6 __END__