Code Monkey home page Code Monkey logo

revbank's Introduction

#!/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) { 
        $readline->callback_read_char if $select->can_read(.05);
        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<RevBank::Plugins> for documentation about writing plugins.

=head1 AUTHOR

Juerd Waalboer <#####@juerd.nl>

=head1 LICENSE

Pick your favorite OSI license.


revbank's People

Contributors

juerd avatar psy0rz avatar praseodym avatar kaliumxyz avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.