Prepaid bar tab CLI
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
lib/RevBank
plugins
.gitignore
README.pod
revbank
revbank.accounts
revbank.market
revbank.plugins
revbank.products

README.pod

#!/usr/bin/perl -w use strict; use attributes; use IO::Select; use List::Util (); use Term::ReadLine; require Term::ReadLine::Gnu; # The other one sucks. use FindBin qw($RealBin); use lib "$RealBin/lib"; use RevBank::Plugins; use RevBank::Global; use RevBank::Messages; use RevBank::Cart; our $VERSION = "2.0"; our %HELP = ( "abort" => "Abort the current transaction", ); my @words; my $one_off = 0; if (@ARGV) { # Non-interactive like in sh: -c command_string @ARGV >= 1 and $ARGV[0] eq '-c' or die "$0 has no switches, except -c."; $one_off = 1; @words = split " ", $ARGV[1]; push @words, @ARGV[3 .. $#ARGV] if @ARGV > 3; push @words, "help" if not @words; } $| = 1; my $readline = Term::ReadLine->new($0); $readline->ornaments('me,md,,'); my $select = IO::Select->new; $select->add(\*STDIN); my $cart = RevBank::Cart->new; sub prompt { my ($prompt, $plugins, @completions) = @_; $prompt =~ s/$/: /; $prompt =~ s/\?: $/? /; my @matches; $readline->Attribs->{completion_entry_function} = sub { my ($word, $state) = @_; @matches = grep /^\Q$word\E/i, @completions if $state == 0; return shift @matches; }; my $done; my $input; $readline->callback_handler_install($prompt, sub { $done = 1; $input = shift; $readline->callback_handler_remove; }); my $begin = my $time = time; while (not $done) { if ($select->can_read(.05)) { $readline->callback_read_char; $begin = $time; } if (time > $time) { $time = time; call_hooks( "prompt_idle", $cart, (@$plugins > 1 ? undef : $plugins->[0]), # >1 plugin = main loop $time - $begin, $readline, ); } } print "\e[0m"; defined $input or return; $input =~ s/^\s+//; # trim leading whitespace $input =~ s/\s+$//; # trim trailing whitespace return $input; } RevBank::Plugins->load; call_hooks("startup"); OUTER: for (;;) { print "\n" if not @words; if (not @words and $cart->changed) { call_hooks("cart_changed", $cart); } my $split_input = 1; my $prompt = "Product ID, amount or command"; my @plugins = RevBank::Plugins->new; my $method = "command"; sub abort { print @_, " " if @_; @words = (); call_hooks "abort", $cart; $cart->empty; { no warnings; redo OUTER; } } PROMPT: { if (not @words) { if ($one_off) { exit if $one_off++ > 1; abort "Incomplete command." if $cart->size; exit; } call_hooks "prompt", $cart, $prompt; my %completions = qw(abort 1); for my $plugin (@plugins) { my $attr = attributes::get( ref $method ? $method : $plugin->can($method) ) or next; my ($tab) = $attr =~ /Tab \( (.*?) \)/x; for my $keyword (split /\s*,\s*/, $tab) { if ($keyword =~ /^&(.*)/) { my $method = $1; @completions{ $plugin->$method } = (); } else { $completions{ $keyword }++; } } } if (delete $completions{USERS}) { $completions{$_}++ for RevBank::Users::names; } if (delete $completions{NOABORT}) { delete $completions{abort}; } my $input = prompt $prompt, \@plugins, keys %completions; call_hooks "input", $cart, $input, $split_input; length $input or redo PROMPT; @words = ($split_input ? split(" ", $input) : $input); $split_input = 0; # Only split 'outer' input. } WORD: for (;;) { redo PROMPT if not @words; abort if grep $_ eq 'abort', @words; my $word = shift @words; PLUGIN: for my $plugin (@plugins) { my ($rv, @rvargs) = eval { $plugin->$method($cart, $word) }; if ($@) { call_hooks "plugin_fail", $plugin->id, $@; abort; } if (not defined $rv) { call_hooks "plugin_fail", $plugin->id, "No return code"; abort; } if (not ref $rv) { $prompt = $rv; @plugins = $plugin; ($method) = @rvargs; call_hooks "plugin_fail", $plugin->id, "No method supplied" if not ref $method; abort "Incomplete command." if $one_off and not @words; next WORD; } if ($rv == ABORT) { abort(@rvargs); } if ($rv == REJECT) { my ($reason) = @rvargs; call_hooks "reject", $plugin->id, $reason, @words ? 1 : 0; abort if @words; redo PROMPT; } if ($rv == ACCEPT) { next OUTER; } if ($rv == NEXT) { next PLUGIN if $method eq 'command'; call_hooks "plugin_fail", $plugin->id, "Only 'command' " . "should ever return NEXT."; abort; } call_hooks "plugin_fail", $plugin->id, "Invalid return value"; abort; } call_hooks "invalid_input", $cart, $word; abort if @words; redo OUTER; } } } =head1 NAME revbank - Banking for hackerspace visitors =head1 DESCRIPTION Maybe I'll write some documentation, but not now. Shell-like invocation with C<-c> is supported, sort of, but it has to be a complete command. Currently, multiple commands are supported on the command line (space separated), but that's an unintended feature... =head1 PLUGINS Refer to L for documentation about writing plugins. =head1 AUTHOR Juerd Waalboer <#####@juerd.nl> =head1 LICENSE Pick your favorite OSI license.