From 9d27cf6623b44dbb836eebb37be2460d39bd1709 Mon Sep 17 00:00:00 2001 From: b Date: Sat, 10 Sep 2022 20:49:31 +0000 Subject: [PATCH] More consistent behavior for undefined. Added explanation. --- configure.pl | 304 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 302 insertions(+), 2 deletions(-) diff --git a/configure.pl b/configure.pl index 1caa813..499419f 100755 --- a/configure.pl +++ b/configure.pl @@ -1,7 +1,6 @@ #!/usr/bin/perl # configure.pl - # The new BOTM configuration tool # Copyright (C) 2022 Balthasar Szczepański @@ -19,6 +18,302 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +# This is a tool for managing configuration in a project. +# It was initially based on my configuration script from the BSTA software +# but then I have rewriten it from beginning with new design goals. +# +# The goals: +# - tool to read confinguration files and then apply changes in +# project's source files +# - configure the project at compile time, easy cooperation with makefile +# - possible to keep separately: +# - the actual important settins +# - the settings derived from them +# - the configuration tool +# - the files to configure +# +# Why: +# +# 1. You write a program. You are silently making assumptions. Whenever +# you need a path or other dependency, you define ("hard-code") it there. +# Acceptable for a simple, short, single-use program maybe. +# But terrible for anything more serious. Maintenance and portability +# becomes difficult. When you want to change anything you have to look +# through the whole code to find all places where something is defined. +# A lot of effort and easy to make a mistake. +# +# 2. Instead of hard-coding everything everywhere you can define the assumed +# settings at the begnning of each file using 'const' or '#define' or +# whatever the programming language allows. Even better if you can put the +# definitions into a single file and include them where needed. +# Much better but still for some big projects with many files it takes some +# time and effort to go through all of them. Even if all you wanted was +# to adapt the project to run on a different system for example. +# +# 3. Ok, you could have all definitions in one place and then apply them +# to all the files. One way to do it is to make a script which will read +# the settings from a file and then insert '#define' (C), 'use constant' +# (Perl), or other statements into the files based on pattern matching. +# +# This is of for configuration done at compile time. Not at run time. +# There the program itself has to read the settings and make decisions. +# +# There also exist things like autoconf, automake, etc. see: +# http://www.mrob.com/pub/comp/unix-building-history.html +# However for many project I'm currently dealing with this script +# here is totally enough and so far I did not have to learn these tools. +# They are still a black box (or even black magic) to me. +# +# Usage: +# +# perl configure.pl [configfile1 configfile2 ...] < input_file > output_file +# +# Or './configure.pl' instead of 'perl configure.pl' but then we're making +# the assumption that Perl is installed at '/usr/bin/perl'. +# Of course we can make configure.pl conrigurable by configure.pl and then +# make it reconfigure itself to adapt to where Perl is but I decided to not +# go there. Instead I can handle this on the level of the makefile (which +# is configurable by configure.pl) +# +# The script reads configuration from all files given in the command line +# parameters. The effect is similar to reading a single concatenated file. +# +# Then it reads standard input line by line. +# In each line it looks for replacement patterns, replaces them if found, +# and prints the line to the output. +# +# Configuration files are read once in the same order as the command line +# parameters. +# +# Whenever I write 'whitespace' I mean it can space '' or TAB '\t'. +# +# It is possible to include files like this: +# include path_to_include +# The include statement must start from the first column in the line. +# It is equivalent to replacing the line (with the include statement) +# by the content of the included file +# +# settings can be defined in two ways: +# name: value +# name = value +# These must start from the first column in the line. +# If a line starts with a whitespace it is treated as a continuation +# of the value: +# name: line1 +# line2 +# name = line1 +# line2 +# The name consists of the characters A-Z, a-z, 0-9, '-', '_', and '.'. +# +# In the first format (name: value) the ':' must be immediately after the name. +# The value starts immediately after the first whitespace and is taken directly +# without any processing. +# +# In the second format (name = value) some processing is performed: +# - Any amount (including 0) of whitespace is allowed before and +# after the '+'. +# - Comments starting from '#' are removed. +# - whitespace at beginning and end of each line are removed +# - escaped characters (by '\') are processed. +# - other settings inserted by $name are expanded. +# +# It is possible to insert one setting's value into another one like this: +# s1: 456 +# s2 = 123 $s1 789 +# In this example the value of s2 is '123 456 789'. +# This will not work: +# s2 = 123 $s1 789 +# s1: 456 +# When processing s2 $s1 is not defined yet so the value of s2 is: +# '123 789'. +# +# It is possible to use a value as a function pattern: +# s1: $0 = "$1" +# s2 = $s1(a,b) +# In this example the value of s2 is: 'a = "b"'. +# Here names made entirely from digits are used as the parameters of the +# function pattern and should not be used for other purposes. Possible but +# not recommended. another special name is $_, which joins all parameters +# by ','. +# +# If not provided, the parameters will be treated as empty string. +# So here: +# s1: $0 = "$1" +# s2 = $s1(a) +# the value of s2 is: 'a = ""'. +# +# Even though $0 and $1 were not processed when s1 was defined, they +# were processed when s1 was used as a function pattern. +# +# In this example: +# s1 = $0 = "$1" +# s2 = $s1(a,b) +# s1 will be immediately evaluated to ' = ""', and s2 will also become +# ' = ""'. +# +# Escaping will protece $0 and $2 to be processed too early: +# s1 = \$0 = "\$1" +# s2 = $s1(a,b) +# Here the value of s1 is '$0 = "$1"' again. +# +# The pattern function calls can be nested: +# s1: >$0< +# s2: <$0> +# s3 = $s2($s1(1)) +# In this example this is how it will be processed: +# processing s3, '$s2($s1(1))' +# found call to s2 with parameters '($s1(1))' +# processing s2, '<$0>', '($s1(1))' +# processing parameters '($s1(1))' +# found parameter '$s1(1)' +# processing '$s1(1)' +# found call to s1 with parameters '(1)' +# processing s1, '>$0<', '(1)' +# processing parameters '(1)' +# found parameter '1' +# processing '1' +# return '1' +# value is now '1' +# return ('1') +# found call to $0 with no parameters +# replace $0 with '1' +# value is now '>1<' +# return '>1<' +# replace $s1() with '>1<' +# value is now '>1<' +# return '>1<' +# value is now '>1<' +# return ('>1<') +# found call to $0 with no parameters +# replace $0 with '>1<' +# value is now '<>1<>' +# return '<>1<>' +# replace $2() with '<>1<>' +# value is now '<>1<>' +# return '<>1<>' +# +# So when processing a value, if a pattern function call is found, +# then first, the parameters are separated, and each is processed one +# recursion level deeper. +# then the pattern is processed (also one recursion level deeper than the +# base value) and the $0, etc. are replaced by the (already processed) +# parameters. +# +# Empty parameter list can be useful for separating from text afterwards: +# s1: 456 +# s2 = 123$s1()789 +# Here the value of s2 is: '123456789'. +# Without the '()': +# s1: 456 +# s2 = 123$s1789 +# Here the value of s2 is: '123' because $s1789 was not found. +# +# Escaping the '(' prevents interpreting it as some parameters: +# s1: 456 +# s2 = (123)$s1\(789) +# Here the value of s2 is: '(123)456(789)'. +# Without the '\(': +# s1: 456 +# s2 = (123)$s1(789) +# Here the value of s2 is: '(123)456' because '789' was given to s1 +# as $0 and ignored. +# +# There are special functions and values: +# $_ESCAPE is a function which will escape some characters with '\'. +# $0 - the text to be escaped +# $1 - the regexp which specifies which characters should be escaped +# if not specified then a default regexp is used +# $_URL_ENCODE is a function which will perform percent-encoding +# $0 - the text to be encoded +# $1 - the regexp which specifies which characters should be encoded +# if not specified then a default regexp is used +# $_SPRINTF is a call to the Perl sprintf() function +# $0 - the format string +# $1 and continuing - the parameters to sprintf() +# $_PATH is a function which will join a path from segments +# $0 and continuing - the segments to be joined +# $_PATH_SEPATATOR is the path separator used by $_PATH. +# can be overwritten, default value '/'. +# $_REPLACE_LINE is the pattern to look for in files, to replace the +# entire line. Can be overwritten. Default value '###$0:' +# $0 - the name of the setting +# $REPLACE_KEYWORD is the pattern to look for in files, to replace +# just the pattern. Can be overwritten. Default value '###$0;' +# $0 - the name of the setting +# More functions and values can be added in future versions. +# +# Inserting settings to source files: +# +# After all settings are read, the actual source is processed line by line. +# In each line the tool will look for patterns to replace the entire line. +# The patterns are built by inserting settings names to $REPLACE_LINE. +# For example if: +# REPLACE_LINE: ###$0: +# s1: ABC +# then each line containing '###s1:' will be replaced by 'ABC', and so on. +# The patterns are checked in unpredictable order (because Perl hash). +# If any match is found, the line is replaced and the tool moves to next line. +# If none of the line patterns are matched then comes next step: +# The tool will now look for patterns to replace in the line. +# The patterns are built by inserting settings names to $REPLACE_KEYWORD. +# For example if: +# REPLACE_KEYWORD: ###$0; +# s3: XYZ +# then each occurence of '###s3:' will be replaced by 'XYZ', and so on. +# The patterns are checked in unpredictable order (because Perl hash) +# +# Use in project: +# +# Either install the tool somewhere on the system and multiple projects +# can call it, or copy the tool as part of the project +# +# See the makefile of the tool's project as a simple example. +# +# Of course if you found this tool as part of a different project then +# you probably will not see the original makefile. +# Instead maybe check how the tool is used in the project. +# +# Some ideas: +# +# It's a good idea to use the tool together with the makefile, to compile +# the project. +# +# Generate source files and then use them: +# +# abc: abc.c +# gcc -o abc abc.c +# +# abc.c: abc.1.c settings.txt configure.pl +# perl configure.pl settings.txt < abd.1.c > abc.c +# +# Of course not only the source files but also the makefile itself can be +# configured: +# +# makefile: makefile.1.mak settings.txt configure.pl +# perl configure.pl settings.txt < makefile.1.mak > makefile +# +# If the settings have changed then any attept to make something will +# first regenerate the makefile and then use it. +# +# Use different settings for different targets: +# +# ifndef TARGET +# TARGET = debug +# endif +# +# SETTINGS = settings-$(TARGET).txt settings.txt +# +# makefile: makefile.1.mak $(SETTINGS) configure.pl +# perl configure.pl $(SETTINGS) < makefile.1.mak > makefile +# +# Then use: +# make TARGET=targetname something_to_make +# +# Or even replace the default target by a configurable pattern. +# then, to set a new target use: +# make TARGET=targetname -B makefile +# And afterwards the new tatget will be the default one. + use strict; use constant MAX_DEPTH => 256; @@ -297,6 +592,7 @@ sub parse_pattern { if ($depth >= MAX_DEPTH) { return '$'.$name.$parameters; + # return ''; } my @parameter_list = parse_pattern_parameters($parameters, $depth, %cfg); @@ -343,6 +639,9 @@ sub parse_pattern { } return $parsed; } + # elsif ($name =~ /^[0-9+]$/) { + # return ''; + # } elsif ($name eq ESCAPE) { return escape(@parameter_list); } @@ -357,7 +656,8 @@ sub parse_pattern { return sprintf($format,@parameter_list); } else { - return '$'.$name.$parameters; + # return '$'.$name.$parameters; + return ''; } } -- 2.30.2