#!/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/; }