perlconfig.pl (8822B)
1 #!/usr/bin/perl 2 3 # This program is free software: you can redistribute it and/or modify 4 # it under the terms of the GNU General Public License as published by 5 # the Free Software Foundation, either version 3 of the License, or 6 # (at your option) any later version. 7 # 8 # This program is distributed in the hope that it will be useful, 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 # GNU General Public License for more details. 12 # 13 # You should have received a copy of the GNU General Public License 14 # along with this program. If not, see <http://www.gnu.org/licenses/>. 15 # 16 # Copyright 2015 Marcel Denia 17 18 =head1 NAME 19 20 perlconfig.pl 21 22 =head1 SYNOPSIS 23 24 B<perlconfig.pl> [B<-Dsymbol>=I<value>, ...] [B<-dsymbol>=I<value>, ...] I<[files]> 25 26 Generate a configuration file suitable for (cross-)compiling perl 5. 27 28 =head1 OPTIONS 29 30 =over 31 32 =item B<-Dsymbol=value> 33 34 The symbol identified by I<name> will have the literal value I<value>. 35 When generating the configuration file, it's value will be printed enclosed by 36 single quotation marks('). 37 38 =item B<-dsymbol=value> 39 40 The symbol identified by I<name> will have the literal value I<value>. 41 42 =back 43 44 =head1 DESCRIPTION 45 46 B<perlconfig.pl> is a program to compile and generate configuration files ready 47 to be used as a "config.sh" file for compiling perl 5. It does so by processing 48 specially-made configuration files(usually called *.config), typically augmented 49 by command-line definitions. 50 51 B<perlconfig.pl>'s intent is to be used in place of perl 5's own Configure 52 script in situations where it can not be run, like cross-compiling. 53 54 =head2 File syntax 55 56 B<perlconfig.pl>'s configuration files a consist of symbol definitions in 57 different variations, each one assigning a specific symbol identified by a name 58 some value, as well as conditional blocks in order to allow for some 59 flexibility. 60 61 =head3 Symbol names 62 63 A symbol name has to consist entirely of alphanumeric characters as well as 64 the underscore(_) character. In addition, symbol names may be prefixed with an 65 all-lowercase string, separated by a colon(:): 66 67 my:name=value 68 69 Having a zero-length prefix string is also valid: 70 71 :name=value 72 73 Symbols prefixed that way are called private symbols. They act exactly like 74 regular symbols, with the only difference being that they will B<not> be written 75 to the final configuration file. 76 77 =head3 Symbol definitions 78 79 A symbol definition is in the form of a simple name/value pair, separated by 80 an equals sign(=): 81 82 name=value 83 84 I<value> can be anything really. However, there are 3 notations, with 85 differences in quoting and interpolation: 86 87 =over 88 89 =item name=foo 90 91 The symbol identified by I<name> will have the literal value I<foo>. 92 93 =item name='foo' 94 95 The symbol identified by I<name> will have the literal value I<foo>. 96 When generating the configuration file, it's value will be printed enclosed by 97 single quotation marks('). 98 99 =item name="foo" 100 101 The symbol identified by I<name> will have the value of I<foo> 102 S<B<after interpolation>>(as described in L</Interpolation>). 103 When generating the configuration file, it's value will be printed enclosed by 104 single quotation marks('). 105 106 =back 107 108 =head3 Conditional blocks 109 110 A conditional block is of the form 111 112 (condition) { 113 ... 114 } 115 116 B<perlconfig.pl> will execute everything enclosed in the curly braces({ and }), 117 or inside the BLOCK in Perl 5 terms, if I<condition> evaluates to any true 118 value. I<condition> will go through interpolation as described in 119 L</Interpolation>. It may contain any valid Perl 5 expression. Some common 120 examples are: 121 122 =over 123 124 =item $name eq 'foo' 125 126 Evaluates to true if configuration symbol I<name> is literally equal to I<foo>. 127 128 =item $name ne 'foo' 129 130 Evaluates to true if configuration symbol I<name> is B<not> literally equal to 131 I<foo>. 132 133 =item defined($name) 134 135 Evaluates to true if configuration symbol I<name> is defined(has any usable 136 value, see L<perlfunc/defined>). 137 138 =back 139 140 Conditional blocks may be nested inside conditional blocks. Note that the 141 opening curl brace({) has to be on the same line as your condition. 142 143 =head3 Comments 144 145 All lines starting with nothing or any number of whitespaces, followed by a 146 hash sign(#), are considered comments and will be completely ignored by 147 B<perlconfig.pl>. 148 149 =head3 Interpolation 150 151 In certain situations(see above), B<perlconfig.pl> will interpolate strings or 152 constructs in order to allow you to refer to configuration symbols or embed 153 code. 154 155 Interpolated strings are subject to the following rules: 156 157 =over 158 159 =item You may not include any single(') or double(") quotation marks. 160 161 You can use \qq in order to include double quotation marks(") in your string. 162 163 =item $name and ${name} reference configuration symbols 164 165 You can easily refer to existing configuration symbols using the commmon $name 166 or ${name} syntax. In case you want to refer to the perl variable named $name, 167 write \$name. This is useful for embedding code. 168 169 =item Perl 5 interpolation rules apply 170 171 Aside from the above, you may include anything that is also valid for an 172 interpolated(qq//) string in Perl 5. For instance, it's perfectly valid to 173 execute code using the @{[]} construct. 174 175 =back 176 177 =head1 EXAMPLES 178 179 As a simple example, consider the following configuration file, named 180 "example.config": 181 182 recommendation='The Perl you want is' 183 ($:maturity eq 'stable') { 184 recommendation="$recommendation Perl 5" 185 } 186 ($:maturity eq 'experimental') { 187 recommendation="$recommendation Perl 6(try Rakudo!)" 188 } 189 190 Executing it using these command-lines will yield the following results: 191 192 =over 193 194 =item $ perlconfig.pl -D:maturity=stable example.config 195 196 recommendation='The Perl you want is Perl 5' 197 198 =item $ perlconfig.pl -D:maturity=experimental example.config 199 200 recommendation='The Perl you want is Perl 6(try Rakudo!)' 201 202 =back 203 204 =head1 AUTHOR 205 206 Marcel Denia <naoir@gmx.net> 207 208 =head1 COPYRIGHT AND LICENSE 209 210 Copyright 2015 Marcel Denia 211 212 This program is free software: you can redistribute it and/or modify 213 it under the terms of the GNU General Public License as published by 214 the Free Software Foundation, either version 3 of the License, or 215 (at your option) any later version. 216 217 This program is distributed in the hope that it will be useful, 218 but WITHOUT ANY WARRANTY; without even the implied warranty of 219 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 220 GNU General Public License for more details. 221 222 You should have received a copy of the GNU General Public License 223 along with this program. If not, see <http://www.gnu.org/licenses/>. 224 225 =cut 226 227 use strict; 228 use warnings; 229 use List::Util qw/all/; 230 my $symbol_name_prefix_regex = '(?:[a-z]*:)'; 231 my $symbol_name_regex = "($symbol_name_prefix_regex?(?:[a-zA-Z0-9_]+))"; 232 233 my %config; 234 235 sub interpolate { 236 my $string = shift; 237 my %options = @_; 238 239 # First, convert $foo into ${foo} 240 $string =~ s/(?<!\\)\$$symbol_name_regex/\${$1}/gs; 241 242 # Make ${foo} into $config{'foo'}->{value} 243 $string =~ s/\$\{$symbol_name_regex\}/\$config{\'$1\'}->{value}/g; 244 245 # Un-escape \$foo 246 $string =~ s/\\\$/\$/g; 247 248 # Turn \qq into " 249 $string =~ s/\\qq/\\"/g; 250 251 return $string; 252 } 253 254 # Parse command-line symbol definitions 255 while ($ARGV[0]) { 256 if ($ARGV[0] =~ /^-([D|d])$symbol_name_regex=(.*)$/) { 257 $config{$2} = { value => $3, quoted => $1 eq 'D' }; 258 shift(@ARGV); 259 } 260 else { 261 last; 262 } 263 } 264 265 # Process configuration files 266 my @condition_stack = ( 1 ); 267 for my $file (@ARGV) { 268 open(my $fh, '<', $file) or die "Can't open $file: $!\n"; 269 while (my $line = <$fh>) { 270 chomp($line); 271 272 if ($line =~ /^\s*$symbol_name_regex=(.*)$/) { # A symbol definition 273 if (all {$_ == 1} @condition_stack) { 274 my $symbol = $1; 275 (my $quote_begin, my $value, my $quote_end) = $2 =~ /^(['|"])?([^'"]*)(['|"])?$/; 276 277 $quote_begin = '' unless defined $quote_begin; 278 $quote_end = '' unless defined $quote_end; 279 die "$file:$.: Unmatched quotes in \"$line\"\n" unless $quote_begin eq $quote_end; 280 281 if ($quote_begin eq '"') { 282 $config{$symbol} = { value => eval('"' . interpolate($2) . '"'), quoted => 1 }; 283 } 284 else { 285 $config{$symbol} = { value => $2, quoted => $quote_begin eq '\'' }; 286 } 287 } 288 } 289 elsif ($line =~ /^\s*\((.*)\)\s?{$/) { # A conditional block 290 if (eval(interpolate($1))) { 291 push(@condition_stack, 1); 292 } 293 else { 294 push(@condition_stack, 0); 295 } 296 } 297 elsif ($line =~ /^\s*}$/) { # Closing a conditional block 298 pop(@condition_stack); 299 die "$file:$.: Closing non-existent block\n" unless @condition_stack; 300 } 301 elsif ($line =~ (/^\s*$/) || ($line =~ /^\s*#/)) { # An empty line or comment 302 } 303 else { 304 die "$file:$.: Malformed line: \"$line\"\n"; 305 } 306 } 307 } 308 309 # Output 310 for (sort(keys(%config))) { 311 my $quote = $config{$_}->{quoted} ? '\'' : ''; 312 print("$_=$quote$config{$_}->{value}$quote\n") unless $_ =~ /^$symbol_name_prefix_regex/; 313 }