Skip to content

Commit 602cd50

Browse files
author
Chris White
committed
Added <?!...?> (external cmd) and -k (keep going)
1 parent 9cbc1f4 commit 602cd50

File tree

5 files changed

+179
-36
lines changed

5 files changed

+179
-36
lines changed

README.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ Usage
2828
Perl code of the input files.
2929
-E, --debug Don't evaluate Perl code, just write
3030
it to STDERR.
31-
-s, --set name=value As -D, but gneerates into %S and does
31+
-k, --keep-going Don't stop on errors in an external command
32+
-s, --set name=value As -D, but generates into %S and does
3233
not substitute in the text body.
3334
-h, --help Usage help.
3435

35-
In a **-D** command, the `value` must be a valid Perl value, e.g., `"foo"`
36+
In a **-D** option, the `value` must be a valid Perl value, e.g., `"foo"`
3637
for a string. This may require you to escape quotes in the **-D** argument,
3738
depending on your shell. E.g., if `-D foo="bar"` doesn't work, try
3839
`-D 'foo="bar"'` (with single quotes around the whole `name=value` part).
@@ -46,9 +47,10 @@ There are several modes, indicated by the character after the `<?`:
4647

4748
<? code mode: Perl code is between the tags.
4849
<?= echo mode: prints a Perl expression
49-
<?: command mode: executed by PerlPP itself (see below)
50+
<?: internal-command mode: executed by PerlPP itself (see below)
5051
<?/ code mode, beginning with printing a line break.
5152
<?# comment mode: everything in <?# ... ?> is ignored.
53+
<?! external mode: everything in <?! ... ?> is run as an external command
5254

5355
The code mode is started by `<?` followed by any number of whitespaces
5456
or line breaks.
@@ -72,6 +74,7 @@ The generated script:
7274
- `use`s `5.010`, `strict`, and `warnings`
7375
- provides constants `true` (=`!!1`) and `false` (=`!!0`) (with `use constant`)
7476
- Declares `my %D` and initializes `%D` based on any **-D** options you provide
77+
- Declares `my %S` and initializes `%S` based on any **-s** options you provide
7578

7679
Other than that, everything in the script comes from your input file(s).
7780
Use the **-E** option to see the generated script.
@@ -140,8 +143,29 @@ produces the output
140143

141144
So `<?/ ... ?>` is effectively a shorthand for `<? print "\n"; ... ?>`.
142145

143-
Commands
144-
--------
146+
### External commands using `<?!`
147+
148+
The example
149+
150+
<?!echo Howdy!?>
151+
152+
produces the output
153+
154+
Howdy!
155+
156+
If the command returns an error status, perlpp will as well, unless you
157+
specify **-k**. That way you can use perlpp and external commands in `make`
158+
and other programs that check exit codes, and not silently lose error
159+
information. For example, running `perlpp` on the input:
160+
161+
<?! false ?> More stuff
162+
163+
will give you an error message (from the `false`'s error return), and will not
164+
print `More stuff`. Running `perlpp -k` on that same input will give the
165+
error message and will print `More stuff`.
166+
167+
Internal Commands
168+
-----------------
145169

146170
### Include
147171

@@ -218,7 +242,8 @@ string `'alphabet '`, so the result will be
218242
ALPHABET
219243
ABCDEFGHIJKLMNOPQRSTUVWXYZ
220244

221-
Capturing works in all modes: code, echo, or command mode.
245+
Capturing works in all modes: code, echo, internal-command, or
246+
external-command mode.
222247

223248
C Preprocessor Emulation
224249
------------------------

perlpp.pl

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# http://darkness.codefu.org/wordpress/2003/03/perl-scoping/
66

77
package PerlPP;
8-
our $VERSION = '0.3.0-alpha';
8+
our $VERSION = '0.3.0-pre.2';
99

1010
use v5.10; # provides // - http://perldoc.perl.org/perl5100delta.html
1111
use strict;
@@ -46,6 +46,7 @@ package PerlPP;
4646
use constant OBMODE_ECHO => 3;
4747
use constant OBMODE_COMMAND => 4;
4848
use constant OBMODE_COMMENT => 5;
49+
use constant OBMODE_SYSTEM => 6; # an external command being run
4950

5051
# Layout of the output-buffer stack.
5152
use constant OB_TOP => 0; # top of the stack is [0]: use [un]shift
@@ -58,6 +59,7 @@ package PerlPP;
5859
my $Package = ''; # package name for the generated script
5960
my $RootSTDOUT;
6061
my $WorkingDir = '.';
62+
my %Opts; # Parsed command-line options
6163

6264
# Vars accessible to, or used by or on behalf of, :macro / :immediate code
6365
my @Preprocessors = ();
@@ -259,6 +261,45 @@ sub ExecuteCommand {
259261
}
260262
} #ExecuteCommand()
261263

264+
sub GetStatusReport {
265+
# Get a human-readable result string, given $? and $! from a qx//.
266+
# Modified from http://perldoc.perl.org/functions/system.html
267+
my $retval;
268+
my $status = shift;
269+
my $errmsg = shift || '';
270+
271+
if ($status == -1) {
272+
$retval = "failed to execute: $errmsg";
273+
} elsif ($status & 127) {
274+
$retval = sprintf("process died with signal %d, %s coredump",
275+
($status & 127), ($status & 128) ? 'with' : 'without');
276+
} elsif($status != 0) {
277+
$retval = sprintf("process exited with value %d", $status >> 8);
278+
}
279+
return $retval;
280+
} # GetStatusReport()
281+
282+
sub ShellOut { # Run an external command
283+
my $cmd = shift =~ s/^\s+|\s+$//gr; # trim leading/trailing whitespace
284+
die "No command provided to @{[TAG_OPEN]}!...@{[TAG_CLOSE]}" unless $cmd;
285+
$cmd = QuoteString $cmd; # note: cmd is now wrapped in ''
286+
287+
my $error_response = ($Opts{KEEP_GOING} ? 'warn' : 'die'); # How we will handle errors
288+
289+
print(
290+
qq{do {
291+
my \$output = qx${cmd};
292+
my \$status = PerlPP::GetStatusReport(\$?, \$!);
293+
if(\$status) {
294+
$error_response("perlpp: command '" . ${cmd} . "' failed: \${status}; invoked");
295+
} else {
296+
print \$output;
297+
}
298+
};
299+
} =~ s/^\t{2}//gmr # de-indent
300+
);
301+
} #ShellOut()
302+
262303
sub OnOpening {
263304
# takes the rest of the string, beginning right after the ? of the tag_open
264305
# returns (withinTag, string still to be processed)
@@ -286,6 +327,8 @@ sub OnOpening {
286327
# OBMODE_CODE
287328
} elsif ( $after =~ /^(?:\s|$)/ ) {
288329
# OBMODE_CODE
330+
} elsif ( $after =~ /^!/ ) {
331+
$insetMode = OBMODE_SYSTEM;
289332
} elsif ( $after =~ /^"/ ) {
290333
die "Unexpected end of capturing";
291334
} else {
@@ -328,6 +371,8 @@ sub OnClosing {
328371
# Ignore the contents - no operation
329372
} elsif ( $insetMode == OBMODE_CODE ) {
330373
print "$inside\n"; # \n so you can put comments in your perl code
374+
} elsif ( $insetMode == OBMODE_SYSTEM ) {
375+
ShellOut( $inside );
331376
} else {
332377
print $inside;
333378
}
@@ -447,19 +492,20 @@ sub OutputResult {
447492
close( $f ) or die $!;
448493
} #OutputResult()
449494

450-
# === Command line ========================================================
495+
# === Command line parsing ================================================
451496

452497
my %CMDLINE_OPTS = (
453-
# hash from internal name to array reference of j
498+
# hash from internal name to array reference of
454499
# [getopt-name, getopt-options, optional default-value]
455500
# They are listed in alphabetical order by option name,
456501
# lowercase before upper, although the code does not require that order.
457502

458-
EVAL => ['e','|eval=s', ""],
503+
EVAL => ['e','|eval=s', ''],
459504
DEBUG => ['d','|E|debug', false],
460505
# -h and --help reserved
461506
# --man reserved
462-
# INPUT_FILENAME assigned by parse_command_line_into
507+
# INPUT_FILENAME assigned by parse_command_line_into()
508+
KEEP_GOING => ['k','|keep-going',false],
463509
OUTPUT_FILENAME => ['o','|output=s', ""],
464510
DEFS => ['D','|define:s%'], # In %D, and text substitution
465511
SETS => ['s','|set:s%'], # Extra data in %S, without text substitution
@@ -517,17 +563,16 @@ sub parse_command_line_into {
517563

518564
# === Main ================================================================
519565
sub Main {
520-
my %opts;
521-
parse_command_line_into \%opts;
566+
parse_command_line_into \%Opts;
522567

523568
# Preamble
524569

525-
$Package = $opts{INPUT_FILENAME};
570+
$Package = $Opts{INPUT_FILENAME};
526571
$Package =~ s/^.*?([a-z_][a-z_0-9.]*).pl?$/$1/i;
527572
$Package =~ s/[^a-z0-9_]/_/gi;
528573
# $Package is not the whole name, so can start with a number.
529574

530-
StartOB();
575+
StartOB(); # Output from here on will be included in the generated script
531576
print "package PPP_${Package};\nuse 5.010;\nuse strict;\nuse warnings;\n";
532577
print "use constant { true => !!1, false => !!0 };\n";
533578

@@ -537,8 +582,8 @@ sub Main {
537582
# as textual representations of expressions.
538583
# The parameters are in %D at runtime.
539584
print "my %D = (\n";
540-
for my $defname (keys %{$opts{DEFS}}) {
541-
my $val = ${$opts{DEFS}}{$defname} // 'true';
585+
for my $defname (keys %{$Opts{DEFS}}) {
586+
my $val = ${$Opts{DEFS}}{$defname} // 'true';
542587
# just in case it's undef. "true" is the constant in this context
543588
$val = 'true' if $val eq '';
544589
# "-D foo" (without a value) sets it to _true_ so
@@ -549,42 +594,42 @@ sub Main {
549594
print ");\n";
550595

551596
# Save a copy for use at generation time
552-
%Defs = map { my $v = eval(${$opts{DEFS}}{$_});
597+
%Defs = map { my $v = eval(${$Opts{DEFS}}{$_});
553598
warn "Could not evaluate -D \"$_\": $@" if $@;
554599
$_ => ($v // true)
555600
}
556-
keys %{$opts{DEFS}};
601+
keys %{$Opts{DEFS}};
557602

558603
# Set up regex for text substitution of Defs.
559604
# Modified from http://www.perlmonks.org/?node_id=989740 by
560605
# AnomalousMonk, http://www.perlmonks.org/?node_id=634253
561-
if(%{$opts{DEFS}}) {
606+
if(%{$Opts{DEFS}}) {
562607
my $rx_search =
563-
'\b(' . (join '|', map quotemeta, keys %{$opts{DEFS}}) . ')\b';
608+
'\b(' . (join '|', map quotemeta, keys %{$Opts{DEFS}}) . ')\b';
564609
$Defs_RE = qr{$rx_search};
565610

566611
# Save the replacement values. If a value cannot be evaluated,
567612
# use the name so the replacement will not change the text.
568613
%Defs_repl_text =
569-
map { my $v = eval(${$opts{DEFS}}{$_});
614+
map { my $v = eval(${$Opts{DEFS}}{$_});
570615
($@ || !defined($v)) ? ($_ => $_) : ($_ => ('' . $v))
571616
}
572-
keys %{$opts{DEFS}};
617+
keys %{$Opts{DEFS}};
573618
}
574619

575620
# Now do SETS: -s or --set, into %S by analogy with -D and %D.
576621

577622
# Save a copy for use at generation time
578-
%Sets = map { my $v = eval(${$opts{SETS}}{$_});
623+
%Sets = map { my $v = eval(${$Opts{SETS}}{$_});
579624
warn "Could not evaluate -s \"$_\": $@" if $@;
580625
$_ => ($v // true)
581626
}
582-
keys %{$opts{SETS}};
627+
keys %{$Opts{SETS}};
583628

584629
# Make the copy for runtime
585630
print "my %S = (\n";
586-
for my $defname (keys %{$opts{SETS}}) {
587-
my $val = ${$opts{SETS}}{$defname};
631+
for my $defname (keys %{$Opts{SETS}}) {
632+
my $val = ${$Opts{SETS}}{$defname};
588633
if(!defined($val)) {
589634
}
590635
$val = 'true' if $val eq '';
@@ -596,20 +641,20 @@ sub Main {
596641
print ");\n";
597642

598643
# Initial code from the command line, if any
599-
print $opts{EVAL}, "\n" if $opts{EVAL};
644+
print $Opts{EVAL}, "\n" if $Opts{EVAL};
600645

601646
# The input file
602-
ProcessFile( $opts{INPUT_FILENAME} );
647+
ProcessFile( $Opts{INPUT_FILENAME} );
603648

604649
my $script = EndOB(); # The generated Perl script
605650

606651
# --- Run it ---
607-
if ( $opts{DEBUG} ) {
652+
if ( $Opts{DEBUG} ) {
608653
print $script;
609654

610655
} else {
611-
StartOB(); # output of the Perl script
612-
my $result; # save any errors from the eval
656+
StartOB(); # Start collecting the output of the Perl script
657+
my $result; # To save any errors from the eval
613658

614659
# TODO hide %Defs and others of our variables we don't want
615660
# $script to access.
@@ -619,7 +664,7 @@ sub Main {
619664
print STDERR $result;
620665
exit 1;
621666
} else { # Save successful output
622-
OutputResult( \EndOB(), $opts{OUTPUT_FILENAME} );
667+
OutputResult( \EndOB(), $Opts{OUTPUT_FILENAME} );
623668
}
624669
}
625670
} #Main()
@@ -682,6 +727,12 @@ =head1 OPTIONS
682727
Don't evaluate Perl code, just write the generated code to STDOUT.
683728
By analogy with the C<-E> option of gcc.
684729
730+
=item -k, --keep-going
731+
732+
Normally, errors in a C<!command> sequence will terminate further
733+
processing. If B<-k> is given, an error message will be printed to stderr,
734+
but the script will keep running.
735+
685736
=item -s, --set B<name>[=B<value>]
686737
687738
As B<-D>, but:

t/cmdline.t

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Tests of perlpp command-line options
33
use strict;
44
use warnings;
5-
use Test::More 'no_plan';
5+
use Test::More;
66
use IPC::Run3;
77
use constant CMD => 'perl perlpp.pl';
88

@@ -96,9 +96,17 @@ my @testcases=(
9696

9797
); #@testcases
9898

99-
#plan tests => scalar @testcases;
100-
# TODO count the out_re and err_re in @testcases, since the number of
99+
# count the out_re and err_re in @testcases, since the number of
101100
# tests is the sum of those counts.
101+
my $testcount = 0;
102+
103+
for my $lrTest (@testcases) {
104+
my ($out_re, $err_re) = @$lrTest[2..3];
105+
++$testcount if defined $out_re;
106+
++$testcount if defined $err_re;
107+
}
108+
109+
plan tests => $testcount;
102110

103111
for my $lrTest (@testcases) {
104112
my ($opts, $testin, $out_re, $err_re) = @$lrTest;

0 commit comments

Comments
 (0)