#!/usr/bin/perl
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# Copyright 2015 Marcel Denia
=head1 NAME
perlconfig.pl
=head1 SYNOPSIS
B [B<-Dsymbol>=I, ...] [B<-dsymbol>=I, ...] I<[files]>
Generate a configuration file suitable for (cross-)compiling perl 5.
=head1 OPTIONS
=over
=item B<-Dsymbol=value>
The symbol identified by I will have the literal value I.
When generating the configuration file, it's value will be printed enclosed by
single quotation marks(').
=item B<-dsymbol=value>
The symbol identified by I will have the literal value I.
=back
=head1 DESCRIPTION
B is a program to compile and generate configuration files ready
to be used as a "config.sh" file for compiling perl 5. It does so by processing
specially-made configuration files(usually called *.config), typically augmented
by command-line definitions.
B's intent is to be used in place of perl 5's own Configure
script in situations where it can not be run, like cross-compiling.
=head2 File syntax
B's configuration files a consist of symbol definitions in
different variations, each one assigning a specific symbol identified by a name
some value, as well as conditional blocks in order to allow for some
flexibility.
=head3 Symbol names
A symbol name has to consist entirely of alphanumeric characters as well as
the underscore(_) character. In addition, symbol names may be prefixed with an
all-lowercase string, separated by a colon(:):
my:name=value
Having a zero-length prefix string is also valid:
:name=value
Symbols prefixed that way are called private symbols. They act exactly like
regular symbols, with the only difference being that they will B be written
to the final configuration file.
=head3 Symbol definitions
A symbol definition is in the form of a simple name/value pair, separated by
an equals sign(=):
name=value
I can be anything really. However, there are 3 notations, with
differences in quoting and interpolation:
=over
=item name=foo
The symbol identified by I will have the literal value I.
=item name='foo'
The symbol identified by I will have the literal value I.
When generating the configuration file, it's value will be printed enclosed by
single quotation marks(').
=item name="foo"
The symbol identified by I will have the value of I
S>(as described in L).
When generating the configuration file, it's value will be printed enclosed by
single quotation marks(').
=back
=head3 Conditional blocks
A conditional block is of the form
(condition) {
...
}
B will execute everything enclosed in the curly braces({ and }),
or inside the BLOCK in Perl 5 terms, if I evaluates to any true
value. I will go through interpolation as described in
L. It may contain any valid Perl 5 expression. Some common
examples are:
=over
=item $name eq 'foo'
Evaluates to true if configuration symbol I is literally equal to I.
=item $name ne 'foo'
Evaluates to true if configuration symbol I is B literally equal to
I.
=item defined($name)
Evaluates to true if configuration symbol I is defined(has any usable
value, see L).
=back
Conditional blocks may be nested inside conditional blocks. Note that the
opening curl brace({) has to be on the same line as your condition.
=head3 Comments
All lines starting with nothing or any number of whitespaces, followed by a
hash sign(#), are considered comments and will be completely ignored by
B.
=head3 Interpolation
In certain situations(see above), B will interpolate strings or
constructs in order to allow you to refer to configuration symbols or embed
code.
Interpolated strings are subject to the following rules:
=over
=item You may not include any single(') or double(") quotation marks.
You can use \qq in order to include double quotation marks(") in your string.
=item $name and ${name} reference configuration symbols
You can easily refer to existing configuration symbols using the commmon $name
or ${name} syntax. In case you want to refer to the perl variable named $name,
write \$name. This is useful for embedding code.
=item Perl 5 interpolation rules apply
Aside from the above, you may include anything that is also valid for an
interpolated(qq//) string in Perl 5. For instance, it's perfectly valid to
execute code using the @{[]} construct.
=back
=head1 EXAMPLES
As a simple example, consider the following configuration file, named
"example.config":
recommendation='The Perl you want is'
($:maturity eq 'stable') {
recommendation="$recommendation Perl 5"
}
($:maturity eq 'experimental') {
recommendation="$recommendation Perl 6(try Rakudo!)"
}
Executing it using these command-lines will yield the following results:
=over
=item $ perlconfig.pl -D:maturity=stable example.config
recommendation='The Perl you want is Perl 5'
=item $ perlconfig.pl -D:maturity=experimental example.config
recommendation='The Perl you want is Perl 6(try Rakudo!)'
=back
=head1 AUTHOR
Marcel Denia
=head1 COPYRIGHT AND LICENSE
Copyright 2015 Marcel Denia
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
=cut
use strict;
use warnings;
use List::Util qw/all/;
my $symbol_name_prefix_regex = '(?:[a-z]*:)';
my $symbol_name_regex = "($symbol_name_prefix_regex?(?:[a-zA-Z0-9_]+))";
my %config;
sub interpolate {
my $string = shift;
my %options = @_;
# First, convert $foo into ${foo}
$string =~ s/(?{value}
$string =~ s/\$\{$symbol_name_regex\}/\$config{\'$1\'}->{value}/g;
# Un-escape \$foo
$string =~ s/\\\$/\$/g;
# Turn \qq into "
$string =~ s/\\qq/\\"/g;
return $string;
}
# Parse command-line symbol definitions
while ($ARGV[0]) {
if ($ARGV[0] =~ /^-([D|d])$symbol_name_regex=(.*)$/) {
$config{$2} = { value => $3, quoted => $1 eq 'D' };
shift(@ARGV);
}
else {
last;
}
}
# Process configuration files
my @condition_stack = ( 1 );
for my $file (@ARGV) {
open(my $fh, '<', $file) or die "Can't open $file: $!\n";
while (my $line = <$fh>) {
chomp($line);
if ($line =~ /^\s*$symbol_name_regex=(.*)$/) { # A symbol definition
if (all {$_ == 1} @condition_stack) {
my $symbol = $1;
(my $quote_begin, my $value, my $quote_end) = $2 =~ /^(['|"])?([^'"]*)(['|"])?$/;
$quote_begin = '' unless defined $quote_begin;
$quote_end = '' unless defined $quote_end;
die "$file:$.: Unmatched quotes in \"$line\"\n" unless $quote_begin eq $quote_end;
if ($quote_begin eq '"') {
$config{$symbol} = { value => eval('"' . interpolate($2) . '"'), quoted => 1 };
}
else {
$config{$symbol} = { value => $2, quoted => $quote_begin eq '\'' };
}
}
}
elsif ($line =~ /^\s*\((.*)\)\s?{$/) { # A conditional block
if (eval(interpolate($1))) {
push(@condition_stack, 1);
}
else {
push(@condition_stack, 0);
}
}
elsif ($line =~ /^\s*}$/) { # Closing a conditional block
pop(@condition_stack);
die "$file:$.: Closing non-existent block\n" unless @condition_stack;
}
elsif ($line =~ (/^\s*$/) || ($line =~ /^\s*#/)) { # An empty line or comment
}
else {
die "$file:$.: Malformed line: \"$line\"\n";
}
}
}
# Output
for (sort(keys(%config))) {
my $quote = $config{$_}->{quoted} ? '\'' : '';
print("$_=$quote$config{$_}->{value}$quote\n") unless $_ =~ /^$symbol_name_prefix_regex/;
}