ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/bin/aemp
Revision: 1.29
Committed: Tue Sep 1 15:11:40 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.28: +3 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.28 #!/OPT/BIn/perl
2 root 1.1
3     =head1 NAME
4    
5     aemp - AnyEvent:MP utility
6    
7     =head1 SYNOPSIS
8    
9     aemp command args...
10    
11     # protocol commands
12 root 1.19 aemp snd <port> <arg...> # send a message
13     aemp mon <port> # wait till port is killed
14     aemp rpc <port> <arg...> # send message, append reply
15     aemp eval <node> <expr...> # evaluate expression
16 root 1.22 aemp trace <nodeid> # trace the network topology
17 root 1.8
18     # run a node
19 root 1.26 aemp run configure_args... # run a node
20 root 1.8
21 root 1.23 # node configuration: node ID
22 root 1.19 aemp setnodeid <nodeid> # configure the real node id
23     aemp delnodeid # reset node id to default (= inherit)
24 root 1.1
25     # node configuration: secret
26 root 1.19 aemp gensecret # generate a random shared secret
27     aemp setsecret <secret> # set the shared secret
28     aemp delsecret # remove the secret (= inherit)
29 root 1.1
30     # node configuration: TLS
31 root 1.19 aemp gencert # generate a random certificate
32     aemp setcert <file> # set a certificate (key.pem + certificate.pem)
33     aemp delcert # remove certificate (= inherit)
34 root 1.8
35 root 1.17 # node configuration: seed addresses for bootstrapping
36 root 1.19 aemp setseeds <host:port>,... # set seeds
37     aemp delseeds # clear all seeds (= inherit)
38     aemp addseed <host:port> # add a seed
39     aemp delseed <host:port> # remove seed
40 root 1.17
41     # node configuration: bind addresses
42 root 1.19 aemp setbinds <host:port>,... # set binds
43     aemp delbinds # clear all binds (= inherit)
44     aemp addbind <host:port> # add a bind address
45     aemp delbind <host:port> # remove a bind address
46 root 1.8
47     # node configuration: services
48 root 1.19 aemp setservices initfunc,... # set service functions
49     aemp delservices # clear all services (= inherit)
50     aemp addservice <initfunc> # add an instance of a service
51     aemp delservice <initfunc> # delete one instance of a service
52 root 1.6
53 root 1.10 # profile-specific configuration
54     aemp profile <name> <command>... # apply command to profile only
55 root 1.26 aemp setparent <name> # specify a parent profile
56 root 1.25 aemp delparent # clear parent again
57 root 1.19 aemp delprofile <name> # eradicate the named profile
58     aemp showprofile <name> # display given profile
59 root 1.25 aemp showconfig <name> ... # display effective config
60 root 1.10
61 root 1.1 =head1 DESCRIPTION
62    
63 root 1.8 With aemp you can configure various aspects of AnyEvent::MP and its
64 root 1.22 protocol, send various messages and even run a node.
65 root 1.1
66 root 1.23 The F<aemp> utility works like F<cvs>, F<svn> or other commands: the first
67     argument defines which operation (subcommand) is requested, after which
68     arguments for this operation are expected. When a subcommand does not eat
69     all remaining arguments, the remaining arguments will again be interpreted
70     as subcommand and so on.
71    
72     This means you can chain multiple commands, which is handy for profile
73     configuration, e.g.:
74    
75 root 1.27 aemp gensecret profile xyzzy binds 4040,4041 nodeid anon/
76    
77     Please note that all C<setxxx> subcommands have an alias without the
78     C<set> prefix.
79 root 1.23
80     =head2 RUNNING A NODE
81    
82     This can be used to run a node - together with some services, this makes
83     it unnecesary to write any wrapper programs.
84    
85     =over 4
86    
87     =item run <profile> <...>
88    
89 root 1.26 Runs a node by calling C<AnyEvent::MP::Kernel::configure> with the given
90 root 1.23 arguments. The node runs under L<AnyEvent::Watchdog>, can be restarted
91     (and autorestarted, see the L<AnyEvent::Watchdog> manual).
92    
93     Care has been taken to load (almost) no modules other than
94     L<AnyEvent::Watchdog> and the modules it loads, so everything (including
95     the L<AnyEvent::MP> modules themselves) will be freshly loaded on restart,
96     which makes upgrading everything except the perl binary easy.
97    
98     =back
99    
100     =head2 PROTOCOL COMMANDS
101    
102     These commands actually communicate with other nodes. They all use a node
103 root 1.25 profile name of C<aemp> (specifying a dfeault node ID of C<anon/> and a
104     binds list containing C<*:*> only).
105 root 1.23
106     They all use a timeout of five seconds, after which they give up.
107    
108     =over 4
109    
110     =item snd <port> <arguments...>
111    
112     Simply send a message to the given port - where you get the port ID from
113     is your problem.
114    
115     Exits after ensuring that the message has been delivered to its node.
116    
117     Most useful to take avdantage of some undocumented functionality inside
118     nodes, such as node ports being able to call any method:
119    
120     aemp snd doomed AnyEvent::Watchdog::restart 1
121    
122     =item rpc <port> <arg...>
123    
124     Like F<aemp snd>, but appends a local reply port to the message and waits
125     for a message to it.
126    
127     Any return values will be JSON-encoded and printed separated by commas
128     (kind of like a JSON array without []-brackets).
129    
130     Example: ask the (undocumented) time service of a node for it'S current
131     time.
132    
133     aemp rpc mynode time
134    
135     =item mon <port>
136    
137     Monitors the port and exits when it's monitorign callback is called. Most
138     useful to monitor node ports.
139    
140     Example: monitor some node.
141    
142     aemp mon doomed
143    
144     =item eval <node> <expr...>
145    
146     Joins all remaining arguments into a string and evaluates it on the given
147     node. Return values are handled as with F<aemp rpc>.
148    
149     Example: find the unix process ID of the node called posicks.
150    
151     aemp eval posicks '$$'
152    
153     =item trace <node>
154    
155     Asks the given node for all currently connected nodes, then asks those
156     nodes for the same, thus tracing all node connections.
157    
158     =cut
159    
160     =head2 CONFIGURATION/NODE ID/SECRET/CERTIFICATE
161    
162     These commands deal with rather basic settings, the node ID, the shared
163     secret and the TLS certificate.
164    
165     =over 4
166    
167     =item setnodeid <nodeid>
168    
169     Set the node ID to the given string.
170    
171     =item delnodeid
172    
173     Removes the node ID again, which means it is inherited again from it's
174     parent profile, or stays unset.
175    
176     =item gensecret
177    
178     Generates a random shared secret and sets it. The shared secret is used to
179     authenticate nodes to each other when TLS is not required.
180    
181     =item setsecret <secret>
182    
183     Sets the shared secret tot he given string, which can be anything.
184    
185     =item delsecret
186    
187     Removes the shared secret again, which means it is inherited again from
188     it's parent profile, or stays unset.
189    
190     =item gencert
191    
192     Generates a self-signed certficate and key, and sets it. This works
193     similarly to a shared secret: when all nodes have it, TLS will be used to
194     authenticate and encrypt all traffic.
195    
196     =item setcert <file>
197    
198     Set a node certificate (and optionally any CA certificates) from the given
199     file. The file must contain the key, followed by the certificate, followed
200     by any CA certificates you want to trust, all in PEM format.
201    
202     See L<AnyEvent::TLS> for some more details - this sets the C<cert> and
203     C<ca_cert> options.
204    
205     =item delcert
206    
207     Removes the certificate(s) again, which means it is inherited again from
208     it's parent profile, or stays unset.
209    
210     =back
211    
212     =head2 CONFIGURATION/SEEDS
213    
214     To discover the network you have to specify some seed addresses, which are
215     basically C<host:port> pairs where you expect some long-running nodes. It
216     does no harm to have a node as its own seed (they will eventually be
217     ignored).
218    
219     =item setseeds <host:port>,...
220    
221     Sets or replaces the list of seeds, which must be specified as a
222     comma-separated list of C<host:port> pairs. The C<host> can be a hostname,
223     an IP address, or C<*> to signify all local host addresses. If the
224     C<:port> is omitted, then the default port of C<4040> is assumed.
225    
226     An empty list is allowed.
227    
228     Example: use C<doomed> with default port as only seednode.
229    
230     aemp setseeds doomed
231    
232     =item delseeds
233    
234     Removes the seed list again, which means it is inherited again from it's
235     parent profile, or stays unset.
236    
237     =item addseed <host:port>
238    
239     Adds a single seed address.
240    
241     =item delseed <host:port>
242    
243     Deletes the given seed address, if it exists.
244    
245     =back
246    
247     =head2 CONFIGURATION/BINDS
248    
249     To be able to be reached from other nodes, a node must I<bind> itself
250     to some listening socket(s). The list of these can either bs specified
251     manually, or AnyEvent::MP can guess them. Nodes without any binds are
252     possible to some extent.
253    
254     =over 4
255    
256     =item setbinds <host:port>,...
257    
258     Sets the list of bind addresses explicitly - see the F<aemp setseeds>
259     command for the exact syntax. In addition, a value of C<0> for the port
260     means to use a dynamically-assigned port.
261    
262 root 1.25 Note that the C<*>, C<*:*> or C<*:port> values are very useful here.
263 root 1.23
264     Example: bind on the default port (4040) on all local interfaces.
265    
266     aemp setbinds "*"
267    
268     Example: bind on a random port on all local interfaces.
269    
270 root 1.25 aemp setbinds "*:*"
271 root 1.23
272     Example: resolve "doomed.mydomain" and try to bind on port C<4040> of all
273     IP addressess returned.
274    
275     aep setbinds doomed.mydomain
276    
277     =item delbinds
278    
279     Removes the bind list again, which means it is inherited again from it's
280     parent profile, or stays unset.
281    
282     =item addbind <host:port>
283    
284     Adds a single bind address.
285    
286     =item delbind <host:port>
287    
288     Deletes the given bind address, if it exists.
289    
290     =back
291    
292     =head2 CONFIGURATION/SERVICES
293    
294     Services are modules (or functions) that are automatically loaded (or
295     executed) when a node starts. They are especially useful when used in
296     conjunction with F<aemp run>, to configure which services a node should
297     run.
298    
299     =over 4
300    
301     =item setservices <initfunc>...
302    
303     Sets or replaces the list of services, which must be specified as a
304     comma-separated list.
305    
306     Each entry in the list is interpreted as either a module name to
307     load (when it ends with C<::>) or a function to call (all other
308     cases). The algorithm to find the function is the same as used for C<<
309     L<AnyEvent::MP>::spawn >>.
310    
311     =item delservices
312    
313     Removes the service list again, which means it is inherited again from
314     it's parent profile, or stays unset.
315    
316     =item addservice <initfunc>
317    
318     Adds a single service.
319    
320     =item delservice <initfunc>
321    
322     Deletes the given service, if it exists.
323    
324     =back
325    
326     =head2 CONFIGURATION/PROFILE MANAGEMENT
327    
328     All the above configuration functions by default affect the I<global
329     default configuration>, which is basically used to augment every profile
330     and node configuration.
331    
332     =over 4
333    
334     =item profile <name> ...
335    
336     This subcommand makes the following subcommands act only on a specific
337     named profile, instead of on the global default. The profile is created if
338     necessary.
339    
340     Example: create a C<server> profile, give it a random node name, some seed
341     nodes and bind it on an unspecified port on all local interfaces. You
342     should add some services then and run the node...
343    
344 root 1.25 aemp server setnodeid anon/ setseeds doomed,10.0.0.2:5000 setbinds "*:*"
345 root 1.23
346     =item delprofile <name>
347    
348     Deletes the profile of the given name.
349    
350 root 1.26 =item setparent <name>
351 root 1.24
352     Sets the parent profile to use - values not specified in a profile will be
353     taken from the parent profile (even recursively, with the global default
354     config being the default parent). This is useful to configure profile
355     I<classes> and then to inherit from them for individual nodes.
356    
357     Note that you can specify circular parent chains and even a parent for the
358     global configuration. Neither will do you any good, however.
359    
360     Example: inherit all values not specified in the C<doomed> profile from
361     the C<server> profile.
362    
363 root 1.26 aemp profile doomed setparent server
364 root 1.24
365     =item delparent
366    
367     Removes the parent again from the profile, if any was set, so the profile
368     inherits directly from the global default config again.
369    
370 root 1.23 =item showprofile <name>
371    
372     Shows the values of the given profile, and only those, no inherited
373     values.
374    
375 root 1.25 =item showconfig <name> <key value...>
376 root 1.23
377 root 1.24 Shows the I<effective> config, i.e. the values as used by a node started
378 root 1.25 with the given profile name. Any additional key-value pairs specified
379 root 1.26 augment the configuration, just as with C<configure>.
380 root 1.24
381 root 1.25 If all arguments are omitted, show the global default config.
382 root 1.23
383     =back
384    
385 root 1.1 =cut
386    
387     use common::sense;
388    
389 root 1.19 # should come before anything else, so all modules
390     # will be loaded on each restart
391 root 1.22 BEGIN {
392 root 1.8 if ($ARGV[0] eq "run") {
393     shift;
394    
395 root 1.13 # d'oh
396 root 1.22 require AnyEvent::Watchdog;
397 root 1.19 # now we can load extra modules
398    
399 root 1.29 require AnyEvent::Watchdog::Util;
400     AnyEvent::Watchdog::Util::autorestart (1);
401     AnyEvent::Watchdog::Util::heartbeat (300);
402 root 1.13
403 root 1.8 require AnyEvent;
404 root 1.25 require AnyEvent::MP::Kernel;
405 root 1.26 AnyEvent::MP::Kernel::configure (@ARGV);
406 root 1.8
407 root 1.16 AnyEvent::detect () eq "AnyEvent::Impl::EV"
408 root 1.8 ? EV::loop ()
409     : AE::cv ()->recv;
410     }
411 root 1.22 }
412 root 1.8
413 root 1.1 use Carp ();
414    
415 root 1.17 use JSON::XS;
416    
417 root 1.1 use AnyEvent;
418 root 1.4 use AnyEvent::Util;
419    
420 root 1.8 use AnyEvent::MP;
421 root 1.1 use AnyEvent::MP::Config;
422    
423     sub my_run_cmd {
424     my ($cmd) = @_;
425    
426     my $cv = &run_cmd;
427     my $status = $cv->recv;
428    
429     $status
430     and die "@$cmd: command failed with exit status $status.";
431     }
432    
433     sub gen_cert {
434 root 1.2 my_run_cmd [qw(openssl req
435     -new -nodes -x509 -days 3650
436     -newkey rsa:2048 -keyout /dev/fd/3
437     -batch -subj /CN=AnyEvent::MP
438     )],
439 root 1.5 "<", "/dev/null",
440 root 1.1 ">" , \my $cert,
441     "3>", \my $key,
442 root 1.4 "2>", "/dev/null";
443 root 1.1
444     "$cert$key"
445     }
446    
447 root 1.25 sub init {
448 root 1.26 configure profile => "aemp", nodeid => "anon/";
449 root 1.25 }
450    
451 root 1.10 our $cfg = AnyEvent::MP::Config::config;
452 root 1.8 our $profile = $cfg;
453 root 1.1
454 root 1.11 sub trace {
455     my ($node) = @_;
456     my $cv = AE::cv;
457     my %seen;
458    
459 root 1.28 my $to = AE::timer 15, 0, sub { exit 1 };
460 root 1.11
461 root 1.25 init;
462 root 1.11
463     my $reply = port {
464 root 1.12 my ($node, @neigh) = @_;
465 root 1.11
466 root 1.12 @neigh = grep $_ ne $NODE, @neigh;
467    
468     print +(join " ", $node, @neigh), "\n";
469    
470     for (@neigh) {
471 root 1.11 unless ($seen{$_}++) {
472     $cv->begin;
473     snd $_, up_nodes => $SELF => $_;
474     }
475     }
476    
477     $cv->end;
478     };
479    
480     $cv->begin;
481     snd $reply, seed => $node;
482    
483     $cv->recv;
484     }
485    
486 root 1.10 sub docmd;
487    
488 root 1.1 our %CMD = (
489     snd => sub {
490 root 1.17 my $port = shift @ARGV;
491 root 1.25 init;
492 root 1.1
493 root 1.7 snd $port, @ARGV; @ARGV = ();
494 root 1.1
495     my $cv = AE::cv;
496 root 1.28 my $to = AE::timer 5, 0, sub { exit 1 };
497 root 1.1 mon $port, $cv;
498 root 1.19 my $reply = port sub { &$cv };
499     snd node_of $port, snd => $reply, "message sent successfully";
500 root 1.1
501     print join " ", $cv->recv, "\n";
502     },
503    
504 root 1.7 rpc => sub {
505 root 1.17 my $port = shift @ARGV;
506 root 1.25 init;
507 root 1.7
508     my $cv = AE::cv;
509 root 1.28 my $to = AE::timer 5, 0, sub { exit 1 };
510 root 1.19 snd $port, @ARGV, port { &$cv }; @ARGV = ();
511 root 1.7 mon $port, $cv;
512    
513 root 1.23 print +(substr JSON::XS->new->encode ([$cv->recv]), 1, -1), "\n";
514 root 1.7 },
515    
516 root 1.1 mon => sub {
517 root 1.17 my $port = shift @ARGV;
518 root 1.25 init;
519 root 1.1
520     mon $port, my $cv = AE::cv;
521     print join " ", $cv->recv, "\n";
522     },
523    
524 root 1.19 eval => sub {
525     my $node = node_of shift @ARGV;
526     my $expr = join " ", @ARGV; @ARGV = ();
527 root 1.25 init;
528 root 1.19
529     my $cv = AE::cv;
530 root 1.28 my $to = AE::timer 5, 0, sub { exit 1 };
531 root 1.19 AnyEvent::MP::Kernel::eval_on $node, $expr, port { &$cv };
532     mon $node, $cv;
533    
534     my ($err, @res) = $cv->recv;
535    
536     die $err if length $err;
537    
538     print +(substr JSON::XS->new->encode (\@res), 1, -1), "\n";
539     },
540    
541 root 1.11 trace => sub {
542     @ARGV >= 1
543 root 1.17 or die "node id missing\n";
544 root 1.11
545 root 1.17 trace shift @ARGV;
546 root 1.11 },
547    
548 root 1.17 setnodeid => sub {
549 root 1.8 @ARGV >= 1
550     or die "shared secret missing\n";
551    
552 root 1.17 $profile->{nodeid} = shift @ARGV;
553 root 1.8 ++$cfg->{dirty};
554     },
555 root 1.17 delnodeid => sub {
556     delete $profile->{nodeid};
557 root 1.8 ++$cfg->{dirty};
558     },
559    
560 root 1.1 setsecret => sub {
561 root 1.8 @ARGV >= 1
562 root 1.1 or die "shared secret missing\n";
563    
564 root 1.8 $profile->{secret} = shift @ARGV;
565 root 1.1 ++$cfg->{dirty};
566     },
567     gensecret => sub {
568 root 1.18 $profile->{secret} = AnyEvent::MP::Kernel::alnumbits AnyEvent::MP::Kernel::nonce 64;
569 root 1.1 ++$cfg->{dirty};
570     },
571 root 1.17 delsecret => sub {
572 root 1.8 delete $profile->{secret};
573 root 1.1 ++$cfg->{dirty};
574     },
575    
576     setcert => sub {
577 root 1.8 @ARGV >= 1
578 root 1.1 or die "key+certificate pem filename missing\n";
579    
580     open my $fh, "<", $ARGV[0]
581     or die "$ARGV[0]: $!";
582    
583     local $/;
584 root 1.8 $profile->{cert} = <$fh>;
585 root 1.1 ++$cfg->{dirty};
586     },
587     gencert => sub {
588 root 1.8 $profile->{cert} = gen_cert;
589 root 1.1 ++$cfg->{dirty};
590     },
591 root 1.17 delcert => sub {
592 root 1.8 delete $profile->{cert};
593 root 1.1 ++$cfg->{dirty};
594     },
595 root 1.6
596 root 1.17 setbinds => sub {
597 root 1.19 @ARGV >= 1
598     or die "bind addresses missing\n";
599     $profile->{binds} = [split /,/, shift @ARGV];
600 root 1.17 ++$cfg->{dirty};
601     },
602     delbinds => sub {
603     delete $profile->{binds};
604     ++$cfg->{dirty};
605     },
606     addbind => sub {
607     @ARGV >= 1
608     or die "bind address missing\n";
609     my $bind = shift @ARGV;
610    
611     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
612     push @{ $profile->{binds} }, $bind;
613     ++$cfg->{dirty};
614     },
615     delbind => sub {
616     @ARGV >= 1
617     or die "bind address missing\n";
618     my $bind = shift @ARGV;
619    
620     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
621     ++$cfg->{dirty};
622     },
623    
624 root 1.6 setseeds => sub {
625 root 1.19 @ARGV >= 1
626     or die "seed addresses missing\n";
627     $profile->{seeds} = [split /,/, shift @ARGV];
628 root 1.6 ++$cfg->{dirty};
629     },
630 root 1.17 delseeds => sub {
631     delete $profile->{seeds};
632     ++$cfg->{dirty};
633     },
634 root 1.6 addseed => sub {
635 root 1.10 @ARGV >= 1
636 root 1.17 or die "seed address missing\n";
637 root 1.6 my $seed = shift @ARGV;
638 root 1.10
639 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
640     push @{ $profile->{seeds} }, $seed;
641     ++$cfg->{dirty};
642     },
643     delseed => sub {
644 root 1.10 @ARGV >= 1
645 root 1.17 or die "seed address missing\n";
646 root 1.8 my $seed = shift @ARGV;
647 root 1.10
648 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
649     ++$cfg->{dirty};
650     },
651    
652     setservices => sub {
653 root 1.19 @ARGV >= 1
654     or die "service specifications missing\n";
655     $profile->{services} = [split /,/, shift @ARGV];
656 root 1.6 ++$cfg->{dirty};
657     },
658 root 1.17 delservices => sub {
659     delete $profile->{services};
660     ++$cfg->{dirty};
661     },
662 root 1.9 addservice => sub {
663 root 1.10 @ARGV >= 1
664     or die "service specification missing\n";
665 root 1.8 my $service = shift @ARGV;
666     push @{ $profile->{services} }, $service;
667     ++$cfg->{dirty};
668     },
669 root 1.9 delservice => sub {
670 root 1.10 @ARGV >= 1
671     or die "service specification missing\n";
672 root 1.8 my $service = shift @ARGV;
673     for (0 .. $#{ $profile->{services} }) {
674     next unless $profile->{services}[$_] eq $service;
675     splice @{ $profile->{services} }, $_, 1;
676     last;
677     }
678 root 1.6 ++$cfg->{dirty};
679     },
680 root 1.10
681     profile => sub {
682 root 1.19 @ARGV >= 1
683     or die "profile name is missing\n";
684 root 1.10 my $name = shift @ARGV;
685    
686     $profile = $cfg->{profile}{$name} ||= {};
687 root 1.14 ++$cfg->{dirty};
688 root 1.10 },
689     delprofile => sub {
690     @ARGV >= 1
691     or die "profile name is missing\n";
692     my $name = shift @ARGV;
693    
694     delete $cfg->{profile}{$name};
695 root 1.14 ++$cfg->{dirty};
696 root 1.10 },
697 root 1.26 setparent => sub {
698 root 1.24 @ARGV >= 1
699     or die "profile name is missing\n";
700    
701     $profile->{parent} = shift @ARGV;
702     ++$cfg->{dirty};
703     },
704     delparent => sub {
705     delete $profile->{parent};
706     ++$cfg->{dirty};
707     },
708 root 1.17 showprofile => sub {
709     @ARGV >= 1
710     or die "profile name is missing\n";
711     my $name = shift @ARGV;
712    
713     print JSON::XS->new->pretty->encode ($cfg->{profile}{$name} || {});
714     },
715     showconfig => sub {
716     my $name = @ARGV ? shift @ARGV : AnyEvent::MP::Kernel::_nodename;
717    
718 root 1.25 my $profile = AnyEvent::MP::Config::find_profile $name, @ARGV;
719     @ARGV = ();
720 root 1.24
721     # make it look nicer:
722     delete $profile->{profile};
723     delete $profile->{parent};
724    
725     print JSON::XS->new->pretty->encode ($profile);
726 root 1.17 },
727 root 1.25
728     # undocumented
729     _resolve => sub {
730     print +(join ",", (AnyEvent::MP::Kernel::_resolve shift @ARGV)->recv), "\n";
731     },
732 root 1.1 );
733    
734 root 1.27 for (keys %CMD) {
735     $CMD{$1} = $CMD{$_} if /^set(.*)$/;
736     }
737    
738 root 1.1 sub docmd {
739     my $cmd = shift @ARGV;
740    
741     $CMD{$cmd}
742 root 1.23 or die "$cmd: no such aemp command (try perldoc aemp, or man aemp)";
743 root 1.1
744     $CMD{$cmd}();
745     }
746    
747     @ARGV
748 root 1.23 or die "Usage: aemp subcommand ... (try perldoc aemp, or man aemp)\n";
749 root 1.1
750 root 1.19 docmd while @ARGV;
751 root 1.1
752