diff --git a/.gitignore b/.gitignore index 0520c8a..2211702 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -TODO.md -OLD +TODO +*.new diff --git a/Makefile b/Makefile index a8dcc4c..5fb7dd5 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,14 @@ -SHELL := /bin/bash -PATH := $(PATH):UTIL PACKAGE := subrelease -VERSION := 0.11 +VERSION := 0.12 PROJECT := makeutils AUTHORS := R.Jaksa 2001,2021,2023 GPLv3 -CAPTION := minimal subrelease snapshotting -SUBVERS := -BUILT := $(shell echo `date +%Y-%m-%d`) +CAPTION := directory to package snapshotting +SUBVERS := b + +SHELL := /bin/bash +PATH := usr/bin:$(PATH) +PKGNAME := $(PACKAGE)-$(VERSION)$(SUBVERSION) +DATE := $(shell date '+%Y-%m-%d') BIN := getversion subrelease SRC := $(BIN:%=%.pl) @@ -17,8 +19,9 @@ LIB := $(filter-out $(SRC),$(LIB)) all: $(BIN) $(DOC) %: %.pl $(LIB) .version.pl .built.%.pl Makefile - @echo -e '#!/usr/bin/perl\n' > $@ - perlpp-simple $< >> $@ + @echo -e '#!/usr/bin/perl' > $@ + @echo "# $@ generated from $(PKGNAME)/$< $(DATE)" >> $@ + pcpp $< >> $@ @chmod 755 $@ .version.pl: Makefile @@ -31,20 +34,23 @@ all: $(BIN) $(DOC) .PRECIOUS: .built.%.pl .built.%.pl: %.pl $(LIB) .version.pl Makefile - @echo 'our $$BUILT = "$(BUILT)";' > $@ + @echo 'our $$BUILT = "$(DATE)";' > $@ @echo "update $@" -# install symlinks to private ~/bin -install: $(BIN) subls - @echo install symlinks to ~/bin: - @mkdir -p ~/bin - @for i in $+; do ln -sf `pwd`/$$i ~/bin/`basename $$i`; done - @for i in $+; do ls -l --color ~/bin/`basename $$i`; done - @if test ! `echo $$PATH | grep ~/bin`; then echo "~/bin missing in your PATH: $$PATH"; fi - $(DOC): doc/%.md: % | doc $< -h | man2md > $@ +# /map install (also subls) +ifneq ($(wildcard /map),) +install: $(BIN) subls + mapinstall -v /box/$(PROJECT)/$(PKGNAME) /map/$(PACKAGE) bin $^ + +# /usr/local install +else +install: $(BIN) + install $^ /usr/local/bin +endif + clean: rm -f .version.pl rm -f .built.*.pl @@ -53,4 +59,4 @@ mrproper: clean rm -f $(BIN) rm -f $(DOC) -include ~/.gitlab/Makefile.git +-include ~/.github/Makefile.git diff --git a/NEWS b/NEWS index e269b5e..838da85 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +0.12 - fixed Makefile, install to /usr/local + - added EXCLUDE 0.11 - version up to the mkdist 0.8... 0.2 - code cleanup - getversion uses next.pl diff --git a/README.md b/README.md index 173b712..5d4d8f4 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ - # subrelease -Simple tool to snapshot current directory to the package file or into +Simple tool to snapshot the current directory to the package file or into subrelease archive. #### Story -I have used a packing script since around 2000. First it was a few lines shell -script, later a big one with html generator, now small again. I still use it +I have used a packing script since around 2000. First, it was a few lines of shell +script, later a big one with html generator, and now small again. I still use it today, even alongside git. The inspiration came when somebody told me that compressing a file once or -ten-times is the same. +ten times is the same. ![](doc/size.png) @@ -21,16 +20,16 @@ The hit-enter usage is: `subrelease`, `subrelease`, `subrelease`, increment vers `subrelease`, `subrelease`... This is a sequence of several 'small' subreleases which are handled -automatically, then a 'big' release, then next round of subreleases etc. If it -is git, it would be a sequence of commits, then tag (plus release), and next +automatically, then a 'big' release, then the next round of subreleases, etc. If it +is git, it would be a sequence of commits, then tag (plus release), and the next round of commits. When subrelease is used over the git repository, subreleases -are completely independent from the git. +are completely independent of the git. Subrelease itself does: -1. identifies package name, version, subversion and location of archive files (automatically), +1. identifies package name, version, subversion, and location of archive files (automatically), 2. identifies whether now it should be a subrelease or release (automatically), -3. saves package snapshot into archive or a release file (automatically), +3. saves package snapshot into an archive or a release file (automatically), 4. increments subrelease variable value if needed (automatically). @@ -45,7 +44,7 @@ Subrelease saves two types of files: The `.tlz` and `.xlz` are shortcuts for `.tar.lz` and can be extracted with standard tar. Optionally, subrelease can save `.tar.gz`, `.tbz2`, `.tar.zst`, -etc. All these files are saved into parent directory, or into `../tgz` directory +etc. All these files are saved into the parent directory, or the `../tgz` directory if it exists. Typical subrelease history and saved files would be: @@ -55,9 +54,9 @@ Typical subrelease history and saved files would be: #### Variants -Subrelease can archive variants of a package, for instance the "beta" variant, -or David's variant "david" etc. To create a beta variant of package, just set -a flag `VARIANT` to "beta" (when you increment a version). Later when you +Subrelease can archive variants of a package, for instance, the "beta" variant, +or David's variant "david" etc. To create a beta variant of the package, just set +the flag `VARIANT` to "beta" (when you increment a version). Later when you decide to turn off beta status, just remove the flag and call the subrelease. You will have beta release and main release packages, and a common archive for beta and main subreleases: @@ -65,25 +64,25 @@ beta and main subreleases: -Variants flags can be used to create temporary experimental variants of package -to be eventually merged-back later. In following example we copy the -subversion b of package to different directory and set a variant flag on it. -Then on the next subrelease call we got local initial release of variant -"david", and on the second call a subversions archive is created and initiated +Variant flags can be used to create temporary experimental variants of the package +to be eventually merged back later. In the following example, we copy the +subversion b of the package to a different directory and set a variant flag on it. +Then on the next subrelease call, we got the local initial release of the variant +"david", and on the second call, a subversion archive is created and initiated with a new subrelease c. This subrelease c and the main package subrelease c are different. Archives are different as well, although having the same name. -Archive names do not contain variant string, to allow to freely change variant +Archive names do not contain a variant string, to allow to freely change the variant string from subversion to subversion. #### Reserved names -In order to use the same version name by the subrelease and by the application, +To use the same version name by the subrelease and by the application, subrelease has to understand variable definition in particular programming -language, and we have to reserve few variable names, like this for C: +language, and we have to reserve a few variable names, like this for C: ``` c # define PACKAGE "package-name" @@ -103,14 +102,14 @@ $COPYLEFT="(c) The.Author 1999, GPL"; Accompanying script `getversion` can inspect these reserved variables. It checks `VERSION.h` or `CONFIG.h` files, or `VERSION.pl`, `VERSION.py`, etc. for -other languages. See [`getversion -h`](doc/getversion.md) for description of -syntax for other languages. +other languages. See [`getversion -h`](doc/getversion.md) for a description of +the syntax for other languages. For multi-language applications, the `Makefile` can be used to hold variables, which when changed will write them to temporary files for every used language, like `.version.pl`, `.version.py`, etc. -Reserved variables names are (plus few more for config files): +Reserved variable names are (plus a few more for config files):   `PACKAGE` package name,   `VERSION` its version, @@ -120,22 +119,23 @@ Reserved variables names are (plus few more for config files):   `CAPTION` any package notes, message, definition,   `VARIANT` `BRANCH` variant/branch name. -The `SUBVERSION` variable is automatically incremented always when subversion +The `SUBVERSION` variable is automatically incremented always when the subversion creates a new subrelease, thus the application can know which subversion it is. #### Config files -Stealth operation without introducing any new variables definitions to the +Stealth operation without introducing any new variable definitions to the source tree is possible by keeping them instead in the config file -`.subrelease` in current or any parent directory (than you will not add -anything into source tree). +`.subrelease` in the current or any parent directory (than you will not add +anything into the source tree). -Config files also recognize few more variables. To preserve the namespace of +Config files also recognize a few more variables. To preserve the namespace of application code these are config-files only, visible only in `.subrelease` or `VERSION` files:   `SUFFIX` the archive suffix, see [`subrelease -h`](doc/subrelease.md) for the list of suffixes,   `TGZDIR` target directory, instead of the default `../tgz` or `..`, +  `EXCLUDE` exclude specified files from being archived,   `ALWAYS` the script/command to be fired at the and of every `subrelease` run,   `ONREL` `ONRELEASE` script only fired at full release,   `ONSUB` `ONSUBRELEASE` only fired at the subrelease, not at release. @@ -143,20 +143,27 @@ application code these are config-files only, visible only in `.subrelease` or Scripts can be used to automatically: * to sync data to mirror, - * to submit summary to webpage, + * to submit a summary to a webpage, * to run code metrics scripts, * etc. -The `.subrelease` can be found in home directory, current directory or any +The `EXCLUDE` can be used to avoid `.git` subdirs in all project directories by +placing the `.subrelease` file in the top-level projects directory with a line: + +``` +EXCLUDE: .git +``` + +The `.subrelease` can be placed in the home directory, current directory, or any parent directory. The order of variables overriding is: - 1. `~/.subrelease` from home directory, - 2. `./.subrelease` or `../.subrelease` or higher, varibles here override these from 1., - 3. CLI arguments, like `-v` option overrides config values, step 4. depends on its value, + 1. `~/.subrelease` from the home directory, + 2. `./.subrelease` or `../.subrelease` or higher, variables here override these from 1., + 3. CLI arguments, like `-v` option override config values, step 4. depends on its value, 4. `./VERSION.c` or `./VERSION.pl` etc., these are main variables definitions, override 1. to 3., - 5. CLI arguments, finaly CLI again overrides everything. + 5. CLI arguments, finally CLI again overrides everything. -Some variables can be defined is `.subrelease` file and some in `VERSION.c` +Some variables can be defined in the `.subrelease` file and some in the `VERSION.c` file etc. at the same time, to control their visibility. #### See also @@ -172,24 +179,24 @@ installation the `make install` routine is provided. #### Under the hood -Subrelease is written in perl, but is compiled from source files to the final -executable script by preprocessor. The reason for such approach is to have -singlefile script as a result, and at the same time allow source code to be -structured to several files. Perl provides best expressive power and great +Subrelease is written in perl but is compiled from source files to the final +executable script by preprocessor. The reason for such an approach is to have +a singlefile script as a result, and at the same time allow source code to be +structured to several files. Perl provides the best expressive power and great stability, which is (most) important for the maintenance of script in the long -term. And for compactness of executed application script. +term. And for compactness of the executed application script. -Over the time I had different requirements for the script. Sometimes conflicting. -Today my subrelease design summary would be: +Over time I had different requirements for the script. Sometimes conflicting. +Today my subrelease design summary will be: * content-only approach without script's private metadata/database/indexes: - primary knowledge source - filenames in the archive, - secondary knowledge source - variables in the application source code (and configs), * data access with standard tools - `subrelease` is not needed to extract or inspect the archive, - * autodetection first - autodetect everything what is possible to autodetect, + * autodetection first - autodetect everything possible to autodetect, * any-functionality = single-command: - `subrelease` to archive, - `getversion` to inspect, - * can be used either as standalone release tool, or alongside git/github etc. + * can be used either as a standalone release tool or alongside git/github etc.
R.Jaksa 2023
diff --git a/UTIL/perlpp-simple b/UTIL/perlpp-simple deleted file mode 100755 index 30d0aae..0000000 --- a/UTIL/perlpp-simple +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/perl -# simple perl preprocessor - just incorporates include files -# * looks into all subdirectories to find included file -# - in the depth order -# - relative to CWD from where perlpp is run, not where file is located -# * avoids double include -# * -e DIR to exclude directories from search -# * -d for debug messaging -# syntax: -# #include "abc.pl" # comments, any name if quoted -# # include abc.pl comments, only .pl suffix if not quoted, # not needed -# # include abc.pl def.pl # multiple files in one include allowed -# TODO: actually, double include is useful when including inside blocks! - -for(@ARGV) { $DEBUG=1 and $_="" if $_ eq "-d" } -our $CR_="\033[31m"; # color red -our $CG_="\033[32m"; # color green -our $CK_="\033[90m"; # color black -our $CD_="\033[0m"; # color default -sub pr { print STDERR @_ } -sub pf { printf STDERR @_ } - -# --------------------------------------------------------------- EXCLUDED DIRS -our @EXCL; # list of dirs to be excluded - -for(my $i=0;$i<$#ARGV;$i++) { - next if $ARGV[$i] ne "-e" or not -d $ARGV[$i+1]; - push @EXCL,$ARGV[$i+1]; - $ARGV[$i] = $ARGV[$i+1] = "" } - -for(@EXCL) { $_="./$_" if not /^\.\// } # prepend "./" if needed - -# pr "excl: $_\n" for @EXCL; -# ------------------------------------------------ LIST OF DIRS WITH PERL FILES -our @DIRS; # recursive list of local dirs -our %NF; # number of files in each dir -our %FF; # list of perl files in each dir - -# inar \@a,$s; checks whether the string $s is in an array @a -sub inar { my ($a,$s)=@_; for(@{$a}) { return 1 if $_ eq $s } return 0 } - -# pldirs "."; looks for all dirs with .pl files in ".", fills-up @DIRS and %NF -sub pldirs { - my $dir = $_[0]; - my @all; opendir(DIR,$dir); @all=readdir(DIR); closedir(DIR); - my @ff; for(@all) { push @ff,$_ if $_=~/\.pl$/ } # save .pl files names - if(@ff) { push @DIRS,$dir; $NF{$dir}=@ff; $FF{$dir}=\@ff } # save nonempty - for(@all) { - next if /^\./; # skip hidden dirs - my $path = "$dir/$_"; - next if inar \@EXCL,$path; # skip excluded dirs - pldirs($path) if -d $path; }} - -# do look -pldirs "."; - -# pf "%4d: %s\n",$NF{$_},$_ for @DIRS; -# for my $d (@DIRS) { pf "%22s:",$d; pr " $_" for @{$FF{$d}}; pr "\n" } -# ------------------------------------------------------------ SORT SEARCH DIRS - -# 1st compare by number of slashes - to look in current directory first -# 2nd compare by number of .pl files in dir - just speculative speedup -sub compare { - my $ca = $a=~tr/\///; # count of / in $a - my $cb = $b=~tr/\///; # in $b - if ($ca<$cb) { return -1 } - elsif($ca>$cb) { return 1 } - else { - if ($NF{$a}>$NF{$b}) { return -1 } - elsif($NF{$a}<$NF{$b}) { return 1 } - else { return 0 }}} - -# resort DIRS -@DIRS=(); push @DIRS,$_ for sort compare keys %NF; - -# pf "%4d: %s\n",$NF{$_},$_ for @DIRS; -# ------------------------------------------------------------ PROCESS INCLUDES -our @INCLUDED; # list of already included files (to disable double include) - -# line by line add a file to the output, parse # macros -sub addfile { - my $file=$_[0]; - local $in=$_[1]; - - # look for file recursively - my ($path,$ok); # full path and whether found - for my $dir (@DIRS) { $path = "$dir/$file"; - $ok=2 and last if inar \@INCLUDED,$path; # already included - $ok=1 and last if inar $FF{$dir},$file } # found => proceed - - # debug - sub prd { pr "$in$_[0]$_[1]$CD_\n" } - if($DEBUG) { - if ($ok==1) { (my $p=$path)=~s/^\.\///; prd $CG_,$p } # OK - elsif($ok==2) { prd $CK_,$file } # double include - else { prd $CR_,$file }} # not found - - return if $ok!=1; # file not found - push @INCLUDED,$path; # register file - - # parse the file - my $IN1 = qr/^\h*\"([^\"]+)\"\h*/; # quoted include - my $IN2 = qr/^\h*([a-zA-Z0-9\._-]+\.pl)\h*/; # .pl unquoted include - for(split /\n/,`cat $path`) { - - # include lines - if(/^\h*\#\h*include\h+(.*?)$/) { - my $s=$1; my $ok; - while($s=~s/$IN1// or $s=~s/$IN2//) { addfile($1,"$in "); $ok=1 } - next if $ok } - - # regular lines - print "$_\n" }} - -# add each requested file (argument) to the output -for(@ARGV) { - next if $_ eq ""; - addfile $_ } - -# ----------------------------------------------------- R.Jaksa 2000,2023 GPLv3 diff --git a/doc/getversion.md b/doc/getversion.md index d72e8cf..43a0d06 100644 --- a/doc/getversion.md +++ b/doc/getversion.md @@ -88,5 +88,5 @@ examination of present configuration files, or directory names. subrelease -h ### VERSION -subrelease-0.11bt R.Jaksa 2001,2021,2023 GPLv3 (built 2023-08-13) +subrelease-0.12 R.Jaksa 2001,2021,2023 GPLv3 (built 2024-05-10) diff --git a/doc/subrelease.md b/doc/subrelease.md index b355dda..c424546 100644 --- a/doc/subrelease.md +++ b/doc/subrelease.md @@ -44,9 +44,9 @@ autodetected by getversion. The .subrelease file from the home directory and from the first parent directory is used as a config file for the subrelease run. Syntax is the same as for the VERSION file (see getversion -h). - Config-specific keywords are SUFFIX, TGZDIR and keywords ALWAYS, - ONRELEASE and ONSUBRELEASE to define scripts to run. Possible - suffixes for the archive files are: + Config-specific keywords are SUFFIX, TGZDIR, EXCLUDE and keywords + ALWAYS, ONRELEASE and ONSUBRELEASE to define scripts to run. + Possible suffixes for the archive files are: short full suffix +-------------+-----------------------+ @@ -57,7 +57,7 @@ autodetected by getversion. +-------------+-----------------------+ rel. arch. release archive - Script keywords usage examples with automatic variables follows: + Examples of scripts defintion with automatic variables: ALWAYS: echo `date -I` %P %f >> /var/log/subrelease.log ONSUBRELEASE: ~/util/mknews ./Changelog ONRELEASE: scp %f server:/archive/%x/ @@ -67,6 +67,11 @@ autodetected by getversion. %p package name, %P w. version, %b variant/branch, %B w. branch %a authors, %c caption, %x project, %l language %% the % character + + The EXCLUDE allows to exclude specified files from being archived. + Exclude patterns are space separated glob patterns, can be quoted + with double quotes: + EXCLUDE: .git *.png "copy of *" ### RECOVERY In case of error (No space left on device, etc.) the re-packaging @@ -79,5 +84,5 @@ autodetected by getversion. getversion -h ### VERSION -subrelease-0.11bt R.Jaksa 2001,2021,2023 GPLv3 built 2023-08-13 +subrelease-0.12 R.Jaksa 2001,2021,2023 GPLv3 built 2024-05-10 diff --git a/exclude.pl b/exclude.pl new file mode 100644 index 0000000..e5db95d --- /dev/null +++ b/exclude.pl @@ -0,0 +1,57 @@ +# HANDLING EXCLUDE PATTERNS + +# parse exclude lines: space-split, but keep quoted ones; keep the order +sub parse_excluded { + my @new; # output: new per-pattern array + my $i=1; # index of quoted patterns + for my $line (@{$_[0]}) { # input: raw EXCLUDE lines of patterns + + # save quoted patterns + my @quoted; @quoted[$i++]=$1 while $line=~s/"([^"]*)"/__QTD${i}__/; + + # space-split every EXCLUDE line + for my $pat (split(/ /,$line)) { + $pat=$quoted[$1] if $pat=~/^__QTD([0-9]+)__$/; + next if $pat eq ""; + push @new,$pat }} + + return \@new } + +# return only patterns which match some file in current working directory, input=patterns +# TODO: load cwd tree only once, and per-pattern parse using perl regex +sub valid_excluded { + my %valid; # output hash: how many files/dirs are found per pattern + for my $pat (@{$_[0]}) { + + # fixed pattern for the -path variant + my $fix=$pat; + $fix="$fix*" if not $pat=~/\*$/; + $fix="*$fix" if not $pat=~/^\*/; + + my @found; # list of found files/paths + if($pat=~/\//) { @found = split /\n/,`find . -path '$fix'` } + else { @found = split /\n/,`find . -name '$pat'` } + # print "$#found $pat: @found\n"; + $valid{$pat} = $#found+1; } + + return %valid } + +# filter_excluded(\@patterns,\%valid) return only the valid patterns +sub filter_excluded { + my @new; + for my $pat (@{$_[0]}) { + next if not $_[1]->{$pat}; + push @new,$pat } + return \@new } + +# print_excluded(\@patterns,\%valid) return verbose patterns string +sub print_excluded { + my $s; + for my $pat (@{$_[0]}) { + if ($_[1]->{$pat}==0) { $s .= "$CK_$pat$CD_ " } # black - none + elsif($_[1]->{$pat}==1) { $s .= "$CG_$pat$CD_ " } # green - one + else { $s .= "$CR_$pat$CD_ " }} # red - more + $s =~ s/ $//; + return $s } + +# R.Jaksa 2024 GPLv3 diff --git a/getversion b/getversion index 5ba2bd4..97d858a 100755 --- a/getversion +++ b/getversion @@ -1,11 +1,19 @@ #!/usr/bin/perl +# getversion generated from subrelease-0.12/getversion.pl 2024-05-10 -our $BUILT = "2023-08-13"; +# included ".built.getversion.pl" +our $BUILT = "2024-05-10"; +# end ".built.getversion.pl" + +# included ".version.pl" our $PACKAGE = "subrelease"; -our $VERSION = "0.11"; +our $VERSION = "0.12"; our $PROJECT = "makeutils"; our $AUTHOR = "R.Jaksa 2001,2021,2023 GPLv3"; -our $SUBVERSION = "bt"; +our $SUBVERSION = ""; +# end ".version.pl" + +# included "support.pl" # remove NL our sub nonl { chomp $_[0]; return $_[0]; } @@ -115,6 +123,7 @@ sub homecfg { my $fnm = $_[0]; sub cfgval { return $2 if $_[0]=~/(^|\n)\h*$_[1]\h*:\h+(.+?)\h*(\n|$)/ } # R.Jaksa 2009,2023 +# end "support.pl" our $HELP=<> /var/log/subrelease.log) CW(ONSUBRELEASE: ~/util/mknews ./Changelog) CW(ONRELEASE: scp %f server:/archive/%x/) @@ -189,6 +198,11 @@ CONFIG CC(%a) authors, CC(%c) caption, CC(%x) project, CC(%l) language CC(%%) the % character + The EXCLUDE allows to exclude specified files from being archived. + Exclude patterns are space separated glob patterns, can be quoted + with double quotes: + CW(EXCLUDE: .git *.png "copy of *") + RECOVERY In case of error (No space left on device, etc.) the re-packaging might be stopped in the middle. For recovery check CC(.bkp) files in @@ -234,6 +248,7 @@ for($i=0;$i<$#ARGV;$i++) { next if not $ARGV[$i]=~/^[a-zA-Z]/; $REQNAME=$ARGV[$i]; $ARGV[$i]=""; last } +# included "prnt.pl" { # CUSTOM PRINTING my $HDRLEN=20; @@ -257,6 +272,7 @@ my sub prnt_ { my ($c1,$c2,$k,$v,$c) = @_; # prnt "key",$var,"comment"; our sub prnt { prnt_ "",$CD_,@_ } # default color our sub prntg { prnt_ "",$CG_,@_ } # green +our sub prntr { prnt_ $CR_,$CR_,@_ } # red our sub prntfn { prnt_ "",$CK_,$_[0],defgrfn($_[1]),$_[2]; } # filename debug # debug "key",$var,"comment"; @@ -277,12 +293,12 @@ our sub prnt_logo { prnt; } # ---------------------------------------------------------------------------------------- HASH DEBUG -# TODO: explicit order of keys +# for explicit order of keys my @ORDER = ("file","language","package","project","version","subversion","next","variant", "pkgname","fullname","major","minor","mtype","delimiter","dot","pwd","config2", "config1","config","tmpdir","tgzdir","suffix","authors","caption","always", - "onsubrelease","onrelease"); + "onsubrelease","onrelease","exclude"); # prel $key,$val,$msg; prints single element for debughash my sub prel { @@ -318,6 +334,7 @@ our sub proceed { prnt; } # print empty line } # R.Jaksa 2001,2023 GPLv3 +# end "prnt.pl" # wrong args check my @wrong; @@ -331,7 +348,12 @@ our %PKG; # package parameters key-value pairs our %PKK; # exact original keywords for given key our %PKM; # verbose message for given key +# included "config.pl" + +# included "parser.pl" # GETVERSION PARSERS + +# included "tore.pl" # to-regex parser: regexes are hard to read, but simple task-specific language # can be parsed to regex, and such language can be OK to read... # @@ -364,8 +386,9 @@ sub enre { $s =~ s/^\(/((?:^|\\n)/; # first element starts with (?:^|\n) start of line $s =~ s/\)$/(?:\\n|\$))/; # last element ends with (?:\n|$) end of line return $s } +# end "tore.pl" -{ # ---------------------------------------------------------------------------- PER-LASGUAGE PARSERS +{ # ---------------------------------------------------------------------------- PER-LANGUAGE PARSERS # key/value parsers: value = xyzvar(body,key) # $_[0]=body $_[1]=key # return: a value, or (value,used_keyname) pair @@ -455,30 +478,33 @@ our sub getvar { local $CMD = $_[0]; # local to be seen in checkvar local $BODY = $_[1]; $BODY = `cat $PKG{file}` if not defined $_[1]; local $MSG = "<- ".beautify($PKG{file},$PKG{pwd}); $MSG.=" $_[2]" if defined $_[2]; - checkvar "PACKAGE","package"; - checkvar "VERSION","version"; - checkvar "SUBVERS", "subversion"; - checkvar "SUBVERSION","subversion"; - checkvar "PROJECT","project"; - checkvar "AUTHOR", "authors"; - checkvar "AUTHORS", "authors"; - checkvar "COPYRIGHT","authors"; - checkvar "COPYLEFT", "authors"; - checkvar "CAPTION","caption"; - checkvar "BRANCH", "variant"; - checkvar "VARIANT","variant"; - return if not $PKG{config}; # the rest are config specific keywords - checkvar "SUFFIX","suffix"; - checkvar "TGZDIR","tgzdir"; - checkcfg "ONSUBRELEASE","onsubrelease"; # next are arrays = accept multiple lines - checkcfg "ONSUB", "onsubrelease"; - checkcfg "ONRELEASE","onrelease"; - checkcfg "ONREL", "onrelease"; - checkcfg "ALWAYS","always" } + # ------- VARIABLE -------- KEY ----------- + checkvar "PACKAGE", "package"; + checkvar "VERSION", "version"; + checkvar "SUBVERS", "subversion"; + checkvar "SUBVERSION", "subversion"; + checkvar "PROJECT", "project"; + checkvar "AUTHOR", "authors"; + checkvar "AUTHORS", "authors"; + checkvar "COPYRIGHT", "authors"; + checkvar "COPYLEFT", "authors"; + checkvar "CAPTION", "caption"; + checkvar "BRANCH", "variant"; + checkvar "VARIANT", "variant"; + return if not $PKG{config}; # the rest are config specific keywords + checkvar "SUFFIX", "suffix"; + checkvar "TGZDIR", "tgzdir"; + checkcfg "ONSUBRELEASE", "onsubrelease"; # next are checkcfg arrays = accept multiple lines + checkcfg "ONSUB", "onsubrelease"; + checkcfg "ONRELEASE", "onrelease"; + checkcfg "ONREL", "onrelease"; + checkcfg "ALWAYS", "always"; + checkcfg "EXCLUDE", "exclude" } } # ------------------------------------------------------------------------------------------------- # R.Jaksa 2001,2009,2023 GPLv3 +# end "parser.pl" { # SUBRELEASE CONFIG @@ -564,6 +590,8 @@ our sub subrelease_config { } subrelease_config; # R.Jaksa 2023 GPLv3 +# end "config.pl" + prntg "config",beautify($PKG{config1},$PKG{pwd}) if defined $PKG{config1}; prntg "config",beautify($PKG{config2},$PKG{pwd}) if defined $PKG{config2}; @@ -584,6 +612,10 @@ $PKG{suffix}="tlz" and $PKM{suffix}="<= default" if not $PKG{suffix}; fatal "target directory $PKG{tgzdir} does not exist" if not -d $PKG{tgzdir}; # ========================================================================================== IDENTIFY + +# included "identify.pl" + +# included "names.pl" { # SUBRELEASE NAMES/FILENAMES UTILITIES, required %PKG access # parse the string to package, delimiter, version @@ -673,15 +705,18 @@ my sub ziptype { elsif($PKG{suffix} =~ /gz$/) { return "--gzip" } return "--lzip" } +# TODO here add tar exclude options + # tar from given dir: tarfrom(inputdir,inputfileordir,outputtar) our sub tarfrom { my ($dir,$file,$tar,$cc) = @_; my $zip = ziptype; - cmd "tar cf $tar $zip -C $dir $file",$cc; } + cmd "tar cf $tar $TAROPT$zip -C $dir $file",$cc; } # directory tar: tardir(inputdir,outputtar) our sub tardir { my ($dir,$tar) = @_; my $zip = ziptype; - cmd "tar cf $tar $zip $dir"; } + cmd "tar cf $tar $TAROPT$zip $dir"; } } # R.Jaksa 2001,2021,2023 GPLv3 +# end "names.pl" { # GETVERSION IDENTIFY @@ -788,6 +823,7 @@ our sub getversion_identify { } getversion_identify; # R.Jaksa 2001,2021,2023 GPLv3 +# end "identify.pl" # explicit values by CLI args (again, here to override autodetected) $PKG{$_}=$ARG{$_} and $PKM{$_}="<- CLI arg" for keys %ARG; @@ -828,6 +864,8 @@ debugfn "old sub. archive","$PKG{tgzdir}/$archive" if $archive; debugfn "new sub. archive","$PKG{tgzdir}/$ARCHIVE" if $ARCHIVE and $ARCHIVE ne $archive; # ============================================================================================== NEXT + +# included "next.pl" # this works directly on global %PKG # get_next_sub $release,$archive; inspects release/archive files (relative to tgzdir) @@ -884,6 +922,7 @@ sub get_next_sub { $PKM{next} = "<- $archive"; } # R.Jaksa 2001,2023 GPLv3 +# end "next.pl" # reset subversion if we do RELEASE and there is no ARCHIVE if($RELEASE and not $archive and defined $PKG{subversion} and $PKG{subversion}) { @@ -940,6 +979,85 @@ if(defined $PKG{always}) { prnt "script","$CC_$_$CD_" for @{$PKG{always}}} if(defined $PKG{onsubrelease}) { prnt "subrelease script","$CC_$_$CD_" for @{$PKG{onsubrelease}}} if(defined $PKG{onrelease}) { prnt "release script","$CC_$_$CD_" for @{$PKG{onrelease}}} +# =========================================================================================== EXCLUDE + +# included "exclude.pl" +# HANDLING EXCLUDE PATTERNS + +# parse exclude lines: space-split, but keep quoted ones; keep the order +sub parse_excluded { + my @new; # output: new per-pattern array + my $i=1; # index of quoted patterns + for my $line (@{$_[0]}) { # input: raw EXCLUDE lines of patterns + + # save quoted patterns + my @quoted; @quoted[$i++]=$1 while $line=~s/"([^"]*)"/__QTD${i}__/; + + # space-split every EXCLUDE line + for my $pat (split(/ /,$line)) { + $pat=$quoted[$1] if $pat=~/^__QTD([0-9]+)__$/; + next if $pat eq ""; + push @new,$pat }} + + return \@new } + +# return only patterns which match some file in current working directory, input=patterns +# TODO: load cwd tree only once, and per-pattern parse using perl regex +sub valid_excluded { + my %valid; # output hash: how many files/dirs are found per pattern + for my $pat (@{$_[0]}) { + + # fixed pattern for the -path variant + my $fix=$pat; + $fix="$fix*" if not $pat=~/\*$/; + $fix="*$fix" if not $pat=~/^\*/; + + my @found; # list of found files/paths + if($pat=~/\//) { @found = split /\n/,`find . -path '$fix'` } + else { @found = split /\n/,`find . -name '$pat'` } + # print "$#found $pat: @found\n"; + $valid{$pat} = $#found+1; } + + return %valid } + +# filter_excluded(\@patterns,\%valid) return only the valid patterns +sub filter_excluded { + my @new; + for my $pat (@{$_[0]}) { + next if not $_[1]->{$pat}; + push @new,$pat } + return \@new } + +# print_excluded(\@patterns,\%valid) return verbose patterns string +sub print_excluded { + my $s; + for my $pat (@{$_[0]}) { + if ($_[1]->{$pat}==0) { $s .= "$CK_$pat$CD_ " } # black - none + elsif($_[1]->{$pat}==1) { $s .= "$CG_$pat$CD_ " } # green - one + else { $s .= "$CR_$pat$CD_ " }} # red - more + $s =~ s/ $//; + return $s } + +# R.Jaksa 2024 GPLv3 +# end "exclude.pl" + +$PKG{exclude} = parse_excluded $PKG{exclude}; + +# check applicability of exclude commands +my %valid = valid_excluded $PKG{exclude}; +debugfn "exclude",print_excluded($PKG{exclude},\%valid) if @{$PKG{exclude}} and $DEBUG; + +$PKG{exclude} = filter_excluded $PKG{exclude},\%valid; +prntr "exclude","@{$PKG{exclude}}" if @{$PKG{exclude}} and not $DEBUG; + +# additional tar options (to exclude by glob pattern) +our $TAROPT; +for my $s (@{$PKG{exclude}}) { + if($s=~/[\h\*\?\\]/) { $TAROPT .= "--exclude='$s' " } + else { $TAROPT .= "--exclude=$s " }} + +debugfn "tar options",$TAROPT if $TAROPT and $DEBUG; + # ==================================================================================== ASK TO PROCEED proceed; # ============================================================================ RE/WRITE PACKAGE FILES @@ -1004,6 +1122,7 @@ print "${CK_}cd .$CD_\n"; chdir $PKG{pwd}; print "can't return back to $PKG{pwd} to run scripts\n" if $nscripts and $PKG{pwd} ne nonl(`pwd`); +# included "scripts.pl" # resolve automatic variables in the script command line our sub resolve { @@ -1031,12 +1150,16 @@ our sub resolve { $cmd =~ s/__%__/%/g; return $cmd } +# end "scripts.pl" + if($nscripts) { $PKG{filename} = $tar; # needed for %f cmd resolve($_),$CC_,$CD_ for @{$PKG{always}},@{$PKG{onsubrelease}},@{$PKG{onrelease}}} # ================================================================================ REWRITE SUBVERSION +# included "rewrite.pl" + { # SUBVERSION REWRITE # the SUBVERS(ION) keyword plus whitespaces and begine of line @@ -1097,6 +1220,7 @@ our sub rewrite_sub { writefile $PKG{file},$s if -f $PKG{file}; } } # R.Jaksa 2022 GPLv3 +# end "rewrite.pl" rewrite_sub if not $NOTMP and defined $PKG{file} and -f $PKG{file} @@ -1112,4 +1236,4 @@ print "$CM_$cmd$CD_\n\n$CR_"; system $cmd; print "$CD_\n"; -# ====================================================================== R.Jaksa 2001,2021,2023 GPLv3 +# ================================================================= R.Jaksa 2001,2021,2023,2024 GPLv3 diff --git a/subrelease.pl b/subrelease.pl index 45bb61a..64d375f 100644 --- a/subrelease.pl +++ b/subrelease.pl @@ -49,9 +49,9 @@ The CG(.subrelease) file from the home directory and from the first parent directory is used as a config file for the subrelease run. Syntax is the same as for the CG(VERSION) file (see getversion -h). - Config-specific keywords are SUFFIX, TGZDIR and keywords ALWAYS, - ONRELEASE and ONSUBRELEASE to define scripts to run. Possible - suffixes for the archive files are: + Config-specific keywords are SUFFIX, TGZDIR, EXCLUDE and keywords + ALWAYS, ONRELEASE and ONSUBRELEASE to define scripts to run. + Possible suffixes for the archive files are: CK(short) CK(full suffix) CK(+-------------+-----------------------+) @@ -62,7 +62,7 @@ CK(+-------------+-----------------------+) rel. CK(arch.) release CK(archive) - Script keywords usage examples with automatic variables follows: + Examples of scripts defintion with automatic variables: CW(ALWAYS: echo `date -I` %P %f >> /var/log/subrelease.log) CW(ONSUBRELEASE: ~/util/mknews ./Changelog) CW(ONRELEASE: scp %f server:/archive/%x/) @@ -73,6 +73,11 @@ CC(%a) authors, CC(%c) caption, CC(%x) project, CC(%l) language CC(%%) the % character + The EXCLUDE allows to exclude specified files from being archived. + Exclude patterns are space separated glob patterns, can be quoted + with double quotes: + CW(EXCLUDE: .git *.png "copy of *") + RECOVERY In case of error (No space left on device, etc.) the re-packaging might be stopped in the middle. For recovery check CC(.bkp) files in @@ -251,6 +256,26 @@ if(defined $PKG{onsubrelease}) { prnt "subrelease script","$CC_$_$CD_" for @{$PKG{onsubrelease}}} if(defined $PKG{onrelease}) { prnt "release script","$CC_$_$CD_" for @{$PKG{onrelease}}} +# =========================================================================================== EXCLUDE +# include exclude.pl + +$PKG{exclude} = parse_excluded $PKG{exclude}; + +# check applicability of exclude commands +my %valid = valid_excluded $PKG{exclude}; +debugfn "exclude",print_excluded($PKG{exclude},\%valid) if @{$PKG{exclude}} and $DEBUG; + +$PKG{exclude} = filter_excluded $PKG{exclude},\%valid; +prntr "exclude","@{$PKG{exclude}}" if @{$PKG{exclude}} and not $DEBUG; + +# additional tar options (to exclude by glob pattern) +our $TAROPT; +for my $s (@{$PKG{exclude}}) { + if($s=~/[\h\*\?\\]/) { $TAROPT .= "--exclude='$s' " } + else { $TAROPT .= "--exclude=$s " }} + +debugfn "tar options",$TAROPT if $TAROPT and $DEBUG; + # ==================================================================================== ASK TO PROCEED proceed; # ============================================================================ RE/WRITE PACKAGE FILES @@ -337,4 +362,4 @@ system $cmd; print "$CD_\n"; -# ====================================================================== R.Jaksa 2001,2021,2023 GPLv3 +# ================================================================= R.Jaksa 2001,2021,2023,2024 GPLv3 diff --git a/usr/Makefile b/usr/Makefile new file mode 100644 index 0000000..ad7b0d0 --- /dev/null +++ b/usr/Makefile @@ -0,0 +1,6 @@ + +all: bin/pcpp + +bin/pcpp: /map/pcpp/bin/pcpp + cp $< $@ + diff --git a/usr/bin/pcpp b/usr/bin/pcpp new file mode 100755 index 0000000..90e7f68 --- /dev/null +++ b/usr/bin/pcpp @@ -0,0 +1,495 @@ +#!/usr/bin/perl + +$SIGN = "pcpp-0.4a R.Jaksa 2008,2024 GPLv3"; + +$HELP=< ccc/aaa +# ccc/aaa/bbb/ -> ccc/aaa +sub dirname { my $p=$_[0]; $p=~s/\/*$//; $p=~s/\/[^\/]*$//; return $p } +# end "dirname.pl" + +# included "beautify.pl" +# beautify the path +# for now just remove the leading "./" +sub beautify { my $p=$_[0]; $p=~s/^\.\///; return $p } +# end "beautify.pl" + +for(@ARGV) { if($_ eq "-h") { printhelp $HELP; exit 0 }} +for(@ARGV) { if($_ eq "-v") { $VERBOSE=1; $_=""; last }} +for(@ARGV) { if($_ eq "-vv") { $VERBOSE=2; $_=""; last }} +for(@ARGV) { if($_ eq "-nt") { $NOTRIPLE=1; $_=""; last }} +for(@ARGV) { if($_ eq "-nw") { $NOWATERMARK=1; $_=""; last }} +for(@ARGV) { if($_ eq "-ni") { $NOIND=1; $_=""; last }} +for(@ARGV) { if($_ eq "-l") { $LIST=1; $_=""; last }} +for(@ARGV) { if($_ eq "-l1") { $LIST=2; $_=""; last }} +for(@ARGV) { if($_ eq "-lp") { $LIST=3; $_=""; last }} + +# list of dirs to be excluded +our @EXCL; +for(my $i=0;$i<$#ARGV;$i++) { + next if $ARGV[$i] ne "-e" or not -d $ARGV[$i+1]; + push @EXCL,$ARGV[$i+1]; $ARGV[$i]=$ARGV[$i+1]="" } + +# prepend "./" if needed +for(@EXCL) { $_="./$_" if not /^\.\// and not /^\// } +if($VERBOSE) { pr "${CK_}# exclude$CD_ $CR_$_$CD_\n" for @EXCL } + +# input files +our @FILES; # list of files to be processed +for(@ARGV) { push @FILES,$_ if $_ ne "" } + +# -------------------------------------------- GET LIST OF DIRS WITH INCLUDABLE PERL/C FILES +our @DIRS; # recursive list of all local dirs +our %FF; # per-directory list of all perl/c files +our %NF; # number of files in each dir + +# included "mode.pl" + +# included "sx.pl" +# return file suffix +sub sx { my $s=$_[0]; $s=~s/^.*\.//; return $s } +# end "sx.pl" + +{ + +# return the file mode by the suffix of filename: pl, py or c +my sub getbysx { + return "pl" if $_[0] eq "pl"; + return "py" if $_[0] eq "py"; + return "c" if $_[0] eq "c++"; + return "c" if $_[0] eq "c"; } + +# return the mode by the filename, or return the default perl mode +our sub getmode { + my $sx = sx $_[0]; + my $mode = getbysx $sx; + $mode = "pl" if not $mode; + return $mode } + +# auto-identify the mode of a list of files by the first identifiable +# file suffix, or return the default perl mode +our sub firstmode { + my $mode; + for(@{$_[0]}) { + $mode = getbysx sx $_; + last if $mode } + $mode = "pl" if not $mode; + return $mode } + +# return the comment identifier string by the mode: # or // +our sub getsy { + return "//" if $_[0] eq "c"; + return "#" } + +} # R.Jaksa 2024 GPLv3 +# end "mode.pl" + +our $MODE = firstmode \@FILES; # input file mode (by 1st file): pl, py or c +our $SY = getsy $MODE; # comment identifier +our $SYQ = quotemeta $SY; + +# included "scandirs.pl" +{ # FIND LIST OF DIRS WITH INCLUDABLE PERL/C FILES + +# check if filename is includable pl/py/c file +# according to the MODE and suffix +my sub isinc { + return 1 if not defined $MODE; + return 1 if $MODE eq "pl" and sx($_[0]) eq "pl"; + return 1 if $MODE eq "py" and sx($_[0]) eq "py"; + return 1 if $MODE eq "c" and sx($_[0]) eq "h"; + return 0 } + +# getsubdirs "."; looks for all dirs with .pl/.h files in "." +# fills-up @DIRS and %FF,%NF +our sub getsubdirs { + my $dir = $_[0]; + my @all; opendir(DIR,$dir); @all=readdir(DIR); closedir(DIR); + my @ff; for(@all) { push @ff,$_ if isinc $_ } # save .pl/.c filenames + if(@ff) { push @DIRS,$dir; $NF{$dir}=@ff; $FF{$dir}=\@ff } # save if nonempty + for(@all) { + next if /^\./; # skip hidden dirs + my $path = "$dir/$_"; + next if inar \@EXCL,$path; # skip excluded dirs + getsubdirs($path) if -d $path; }} + +# SORT SEARCH DIRS TO PUT BETTER UP +# 1st compare by number of slashes - to look in current directory first +# 2nd compare by number of .pl/.c files in dir - just speculative speedup +my sub subcompare { + my $ca = $a=~tr/\///; # count of / in $a + my $cb = $b=~tr/\///; # in $b + if ($ca<$cb) { return -1 } + elsif($ca>$cb) { return 1 } + else { + if ($NF{$a}>$NF{$b}) { return -1 } + elsif($NF{$a}<$NF{$b}) { return 1 } + else { return 0 }}} + +# re-sort DIRS +our sub resort { + @DIRS=(); + push @DIRS,$_ for sort subcompare keys %NF } + +} # R.Jaksa 2024 GPLv3 +# end "scandirs.pl" + +getsubdirs "."; # fill-up DIRS, FF and NF +resort(); # re-sort DIRS + +if($VERBOSE>1) { + my $l = $L1+3; + for my $d (@DIRS) { + pf "$CK_# search $CG_%-${l}s$CD_",$d; + pr " $CK_$_$CD_" for @{$FF{$d}}; + pr "\n" }} + +# ------------------------------------------------------------------------- PROCESS INCLUDES +our @INCLUDED; # list of already included files (to disable double include) +# TODO: actually, double include can be useful when including inside blocks! + +# included "include.pl" +{ # ------------------------------------ RESOLVE INCLUDE DIRECTIVES AND ASSEMBLE FULL OUTPUT + +# verbose printout of includes, globals: $L1, $L2, $level, $how +my sub report { + my $path=$_[1]; + my $c1=$_[0]; + $c1=$CG_ if $c1 eq $CC_ and defined $path and $path eq $file; + my $c2=$c1; + $c2=$CG_ if $c1 ne $CK_ and $c1 ne $CM_ and defined $path; + my $sp = " " x ($level-1); + + # cell-lengths logic + my $sl = length($sp); # just the L1-space length + my $ll = length($file)+$sl; # whole L1 length + my $hl = length($how); # whole L2 length + my $l1 = $L1-$sl; # space-corrected L1 length + $l1-= $hl-$L2 if $hl>$L2; # make space for L2 is L1 if needed and possible + my $l2 = $L2; # + $l2-= $ll-$L1 if $ll>$L1; # move L2 left if possible (L1 space available) + + if($VERBOSE) { + pf "$CK_$SY include $sp$c1%-*s$CD_ $CK_%*s$CD_",$l1,$file,$l2,"$how"; + pr " $c2$path$CD_" if defined $path and $path ne $file; + pr "\n" } + if($LIST and $c1 ne $CK_ and $how ne "missing" and $level>=1) { + if ($LIST==3) { print "$sp$path\n" } + elsif($LIST==2) { print "$sp$file\n" if $level==1 } + else { print "$sp$file\n" }}} + +# ------------------------------------------------------------------------------------- MAIN + +# line by line add a file to the output, parse #include directives +our sub addfile { + local $file=$_[0]; + my $rdir=$_[1]; # current relative subdir + local $level=$_[2]; # recursion level + my $indent=$_[3]; # requested additional indentation space for includes + my $ok=0; # 1=alreadyincluded 2=speculativepath 3=filefound + my $path; # full path (to be found) + local $how; # verbose: how was the path found + + $level=0 if not defined $level; # start level zero + $indent="" if not defined $indent; + + # look for file in CWD using direct path if no recursion yet + if(not $level) { + $path = $file; # try direct explicit path + if(inar \@INCLUDED,$path) { $ok=1 } # skip already included + elsif(-f $path) { $ok=3 } + $how = "direct" if $ok } + + # look for file using explicit path relative to parent-file dir + if(not $ok) { + $path = "$rdir/$file"; # try path relative to parent + if(inar \@INCLUDED,$path) { $ok=1 } # already included + elsif(-f $path) { $ok=3 } + $how = "relative" if $ok } + + # look for file recursively (by filename) + if(not $ok) { + my $fn=$file; $fn=~s/^.*\/// if $fn=~/\//; # strip the explicit dir + my $dir; for(@DIRS) { # loop through dirs + $dir = $_; + $path = "$dir/$fn"; # try path relative to every dir + $ok=1 and last if inar \@INCLUDED,$path; # already included + $ok=3 and last if inar $FF{$dir},$fn } # found => proceed + $how = "found" if $ok; + if($ok==3 and $fn ne $file) { # if file contained dirname + my $fd = quotemeta "/".dirname($file); # directory part of the include name + $ok=2 if not $dir =~ /$fd$/ } # is speculative + $how = "guess" if $ok==2 } + + # otherwise missing + if(not $ok) { $how = "missing" } + + # verbose/list + if ($ok==1) { report $CK_,beautify($path) } # double include + elsif($ok==2) { report $CM_,beautify($path) } # speculative + elsif($ok==3) { report $CC_,beautify($path) } # OK + else { report $CR_,$file } # not found + + return if $ok==0; # file not found + return if $ok==1; # file already included (TODO: accept if requested, but avoid recursion) + push @INCLUDED,$path; # register file + $rdir = dirname $path if $ok; # save for the explicit path lookup in next recursion + + # filename regexes + my $IN1 = qr/^\h*\"([^\"]+)\"/; # quoted include + my $IN2; # unquoted include + $IN2 = qr/^\h*([a-zA-Z0-9\._-]+\.pl)/ if $MODE eq "pl"; + $IN2 = qr/^\h*([a-zA-Z0-9\._-]+\.py)/ if $MODE eq "py"; + $IN2 = qr/^\h*([a-zA-Z0-9\._-]+\.h)/ if $MODE eq "c"; + + my @output; # the output line-by-line + + # watermark for new file, at the included-file indentation level + if(not $NOWATERMARK and $level) { + my $ind=$indent; $ind="" if $NOIND; + push @output,"$SY:SEP\n"; + push @output,"$ind$SY included \"$file\"\n" } # TODO: also comment + + # read cuurent file and recursively resolve include directives + for my $line (split /\n/,`cat $path`) { # <- we read files here! + if($line =~ /^(\h*)$SYQ\h*include\h+(.*?)$/) { # identify include line + my $ind=$1; my $s=$2; my $OK=0; + while($s=~s/$IN1// or $s=~s/$IN2//) { # parse it + push @output,addfile($1,$rdir,$level+1,$ind); $OK=1 } # recurse inside + next if $OK } # go for the next line + if($indent and not $NOIND and not $line=~/^\h*$/) { $line="$indent$line" } # indentation + push @output,"$line\n"; } # <- add regular lines here! + + # watermark footer + if(not $NOWATERMARK and$level) { + my $ind=$indent; $ind="" if $NOIND; + push @output,"$ind$SY end \"$file\"\n"; + push @output,"$SY:SEP\n" } + + return @output; } + +} # ---------------------------------------------------------------- R.Jaksa 2023,2024 GPLv3 +# end "include.pl" + +# auxiliary output buffer, as the include recursion would break simple print to stdout, +# we print to the @output buffer instead and only at the end to the stdout +my @output; + +# TODO: header with timestamp and list of inputs +# TODO: #! interpreter identifier + +# add each argv file to the output +push @output,addfile($_) for @FILES; + +# skip the rest in the list mode +exit if $LIST; + +if(1) { +# remove tripled comments +if(not $NOTRIPLE) { + for my $i (0..$#output) { + if(($MODE eq "c" and $output[$i]=~/^\h*\/\/\/[^\/]/) or $output[$i]=~/^\h*\#\#\#[^\#]/) { + $output[$i] = "$SY:DEL $output[$i]"; + my $j=$i-1; while($j>=0 and $output[$j]=~/^\h*$/) { + $output[$j--] = "$SY:DEL\n" }}}} + +# multiple :SEP tags +for my $i (0..$#output-1) { + $output[$i]="$SY:DEL\n" if $output[$i]=~/^$SYQ:SEP/ and $output[$i+1]=~/^$SYQ:SEP/ } + +# surviving :SEP tags, skip if previous/next line is empty, otherwise add new empty line +for my $i (0..$#output) { + next if not $output[$i] =~ /^$SYQ:SEP/; + if($i>0 and $output[$i-1] =~ /^\h*$/) { $output[$i] = "$SY:DEL\n" } + elsif($i<$#output and $output[$i+1] =~ /^\h*$/) { $output[$i] = "$SY:DEL\n" } + else { $output[$i] = "\n" }} + +} +# :DEL tags and assembly of the final output string +my $out; +for my $i (0..$#output) { + if($output[$i] =~ /^$SYQ:DEL/) { next } + $out .= $output[$i] } + +# emit the output +print $out; + +# ------------------------------------------------------------------ R.Jaksa 2000,2024 GPLv3