File Coverage

blib/lib/App/Cleo.pm
Criterion Covered Total %
statement 18 71 25.3
branch 0 30 0.0
condition 0 7 0.0
subroutine 6 10 60.0
pod 1 3 33.3
total 25 121 20.6


line stmt bran cond sub pod time code
1             package App::Cleo;
2              
3 1     1   947 use strict;
  1         2  
  1         47  
4 1     1   6 use warnings;
  1         1  
  1         35  
5              
6 1     1   1115 use Term::ReadKey;
  1         11653  
  1         123  
7 1     1   3210 use Term::ANSIColor qw(colored);
  1         9917  
  1         592  
8 1     1   1391 use File::Slurp qw(read_file);
  1         22039  
  1         83  
9 1     1   2564 use Time::HiRes qw(usleep);
  1         4621  
  1         9  
10              
11             our $VERSION = 0.004;
12              
13             #-----------------------------------------------------------------------------
14              
15             sub new {
16 0     0 0   my $class = shift;
17              
18 0   0       my $self = {
19             shell => $ENV{SHELL} || '/bin/bash',
20             prompt => colored( ['green'], '(%d)$ '),
21             delay => 25_000,
22             @_,
23             };
24              
25 0           return bless $self, $class;
26             }
27              
28             #-----------------------------------------------------------------------------
29              
30             sub run {
31 0     0 1   my ($self, $commands) = @_;
32              
33 0           my $type = ref $commands;
34 0           my @commands = !$type ? read_file($commands)
35 0           : $type eq 'SCALAR' ? split "\n", ${$commands}
36 0 0         : $type eq 'ARRAY' ? @{$commands}
    0          
    0          
37             : die "Unsupported type: $type";
38              
39 0 0         open my $fh, '|-', $self->{shell} or die $!;
40 0           $self->{fh} = $fh;
41 0           ReadMode('raw');
42 0           local $| = 1;
43              
44 0           chomp @commands;
45 0           @commands = grep { /^\s*[^\#;]\S+/ } @commands;
  0            
46              
47             CMD:
48 0           for (my $i = 0; $i < @commands; $i++) {
49              
50 0           my $cmd = $commands[$i];
51 0           chomp $cmd;
52              
53 0 0 0       $self->do_cmd($cmd) and next CMD
54             if $cmd =~ s/^!!!//;
55              
56 0           print sprintf $self->{prompt}, $i;
57              
58 0           my @steps = split /%%%/, $cmd;
59 0           while (my $step = shift @steps) {
60              
61 0           my $key = ReadKey(0);
62 0 0         print "\n" if $key =~ m/[srp]/;
63              
64 0 0         last CMD if $key eq 'q';
65 0 0         next CMD if $key eq 's';
66 0 0         redo CMD if $key eq 'r';
67 0 0         $i--, redo CMD if $key eq 'p';
68              
69 0 0         $step .= ' ' if not @steps;
70 0           my @chars = split '', $step;
71 0   0       print and usleep $self->{delay} for @chars;
72             }
73              
74 0           my $key = ReadKey(0);
75 0           print "\n";
76              
77 0 0         last CMD if $key eq 'q';
78 0 0         next CMD if $key eq 's';
79 0 0         redo CMD if $key eq 'r';
80 0 0         $i--, redo CMD if $key eq 'p';
81              
82 0           $self->do_cmd($cmd);
83             }
84              
85 0           ReadMode('restore');
86 0           print "\n";
87              
88 0           return $self;
89             }
90              
91             #-----------------------------------------------------------------------------
92              
93             sub do_cmd {
94 0     0 0   my ($self, $cmd) = @_;
95              
96 0           my $cmd_is_finished;
97 0     0     local $SIG{ALRM} = sub {$cmd_is_finished = 1};
  0            
98              
99 0           $cmd =~ s/%%%//g;
100 0           my $fh = $self->{fh};
101              
102 0           print $fh "$cmd\n";
103 0           print $fh "kill -14 $$\n";
104 0           $fh->flush;
105              
106             # Wait for signal that command has ended
107 0           until ($cmd_is_finished) {}
108 0           $cmd_is_finished = 0;
109              
110 0           return 1;
111             }
112              
113             #-----------------------------------------------------------------------------
114             1;
115              
116             =pod
117              
118             =head1 NAME
119              
120             App::Cleo - Play back shell commands for live demonstrations
121              
122             =head1 SYNOPSIS
123              
124             use App::Cleo
125             my $cleo = App::Cleo->new(%options);
126             $cleo->run($commands);
127              
128             =head1 DESCRIPTION
129              
130             App::Cleo is the back-end for the L utility. Please see the L
131             documentation for details on how to use this.
132              
133             =head1 CONSTRUCTOR
134              
135             The constructor accepts arguments as key-value pairs. The following keys are
136             supported:
137              
138             =over 4
139              
140             =item delay
141              
142             Number of milliseconds to wait before displaying each character of the command.
143             The default is C<25_000>.
144              
145             =item prompt
146              
147             String to use for the artificial prompt. The token C<%d> will be substituted
148             with the number of the current command. The default is C<(%d)$>.
149              
150             =item shell
151              
152             Path to the shell command that will be used to run the commands. Defaults to
153             either the C environment variable or C.
154              
155             =back
156              
157             =head1 METHODS
158              
159             =over 4
160              
161             =item run( $commands )
162              
163             Starts playback of commands. If the argument is a string, it will be treated
164             as a file name and commands will be read from the file. If the argument is a
165             scalar reference, it will be treated as a string of commands separated by
166             newlines. If the argument is an array reference, then each element of the
167             array will be treated as a command.
168              
169             =back
170              
171             =head1 AUTHOR
172              
173             Jeffrey Ryan Thalhammer
174              
175             =head1 COPYRIGHT
176              
177             Copyright (c) 2014, Imaginative Software Systems
178              
179             =cut