| #!/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 <http://www.gnu.org/licenses/>. |
| # |
| # Copyright 2015 Marcel Denia |
| |
| =head1 NAME |
| |
| perlconfig.pl |
| |
| =head1 SYNOPSIS |
| |
| B<perlconfig.pl> [B<-Dsymbol>=I<value>, ...] [B<-dsymbol>=I<value>, ...] I<[files]> |
| |
| Generate a configuration file suitable for (cross-)compiling perl 5. |
| |
| =head1 OPTIONS |
| |
| =over |
| |
| =item B<-Dsymbol=value> |
| |
| The symbol identified by I<name> will have the literal value I<value>. |
| 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<name> will have the literal value I<value>. |
| |
| =back |
| |
| =head1 DESCRIPTION |
| |
| B<perlconfig.pl> 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<perlconfig.pl>'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<perlconfig.pl>'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<not> 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<value> can be anything really. However, there are 3 notations, with |
| differences in quoting and interpolation: |
| |
| =over |
| |
| =item name=foo |
| |
| The symbol identified by I<name> will have the literal value I<foo>. |
| |
| =item name='foo' |
| |
| The symbol identified by I<name> will have the literal value I<foo>. |
| When generating the configuration file, it's value will be printed enclosed by |
| single quotation marks('). |
| |
| =item name="foo" |
| |
| The symbol identified by I<name> will have the value of I<foo> |
| S<B<after interpolation>>(as described in L</Interpolation>). |
| 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<perlconfig.pl> will execute everything enclosed in the curly braces({ and }), |
| or inside the BLOCK in Perl 5 terms, if I<condition> evaluates to any true |
| value. I<condition> will go through interpolation as described in |
| L</Interpolation>. It may contain any valid Perl 5 expression. Some common |
| examples are: |
| |
| =over |
| |
| =item $name eq 'foo' |
| |
| Evaluates to true if configuration symbol I<name> is literally equal to I<foo>. |
| |
| =item $name ne 'foo' |
| |
| Evaluates to true if configuration symbol I<name> is B<not> literally equal to |
| I<foo>. |
| |
| =item defined($name) |
| |
| Evaluates to true if configuration symbol I<name> is defined(has any usable |
| value, see L<perlfunc/defined>). |
| |
| =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<perlconfig.pl>. |
| |
| =head3 Interpolation |
| |
| In certain situations(see above), B<perlconfig.pl> 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 <naoir@gmx.net> |
| |
| =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 <http://www.gnu.org/licenses/>. |
| |
| =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/(?<!\\)\$$symbol_name_regex/\${$1}/gs; |
| |
| # Make ${foo} into $config{'foo'}->{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/; |
| } |