#!/usr/bin/perl
# configure.pl
-
# The new BOTM configuration tool
# Copyright (C) 2022 Balthasar SzczepaĆski
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# 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;
if ($depth >= MAX_DEPTH) {
return '$'.$name.$parameters;
+ # return '';
}
my @parameter_list = parse_pattern_parameters($parameters, $depth, %cfg);
}
return $parsed;
}
+ # elsif ($name =~ /^[0-9+]$/) {
+ # return '';
+ # }
elsif ($name eq ESCAPE) {
return escape(@parameter_list);
}
return sprintf($format,@parameter_list);
}
else {
- return '$'.$name.$parameters;
+ # return '$'.$name.$parameters;
+ return '';
}
}