From bc1b5e3250223eb944aa85d3e420f400b63e0254 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Fri, 15 Nov 2013 00:51:07 +0000 Subject: latexindent (13nov13) git-svn-id: svn://tug.org/texlive/trunk@32150 c570f23f-e606-0410-a88d-b1316a301751 --- .../scripts/latexindent/defaultSettings.yaml | 181 ++ .../texmf-dist/scripts/latexindent/latexindent.pl | 1784 ++++++++++++++++++++ 2 files changed, 1965 insertions(+) create mode 100755 Master/texmf-dist/scripts/latexindent/defaultSettings.yaml create mode 100755 Master/texmf-dist/scripts/latexindent/latexindent.pl (limited to 'Master/texmf-dist/scripts') diff --git a/Master/texmf-dist/scripts/latexindent/defaultSettings.yaml b/Master/texmf-dist/scripts/latexindent/defaultSettings.yaml new file mode 100755 index 00000000000..457629ce863 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/defaultSettings.yaml @@ -0,0 +1,181 @@ +# +# defaultSettings.yaml +# +# You're welcome to change anything you like in here, but +# it would probably be better to have your own user settings +# files somewhere else- remember that this file may be overwritten +# anytime that you update your distribution. Please see the manual +# for details of how to setup your own settings files. +# +# Please read the manual first to understand what each switch does :) + +# Default value of indentation +defaultIndent: "\t" + +# default file extension of backup file (if original is overwritten with -w switch) +# for example, if your .tex file is called +# myfile.tex +# and you specify the backupExtension as BACKUP.bak then your +# backup file will be +# myfileBACKUP.bak +backupExtension: .bak + +# only one backup per file; if onlyOneBackUp is 0 then the +# number on the extension increments by 1 each time +# (this is in place as a safety measure) myfile.bak0, myfile.bak1, myfile.bak2 +# +# if you set onlyOnebackUp to 1, then the backup file will +# be overwritten each time (not recommended until you trust the script) +onlyOneBackUp: 0 + +# some users may only want a set number of backup files, +# say at most 3; in which case, they can change this switch. +# If maxNumberOfBackUps is set to 0 (or less) then infinitely +# many backups are possible, unless onlyOneBackUp is switched on +maxNumberOfBackUps: 0 + +# indent preamble +indentPreamble: 0 + +# always look for split { }, which means that the user doesn't +# have to complete checkunmatched, checkunmatchedELSE +alwaysLookforSplitBraces: 1 + +# always look for split [ ], which means that the user doesn't +# have to complete checkunmatchedbracket +alwaysLookforSplitBrackets: 1 + +# remove trailing whitespace from all lines +removeTrailingWhitespace: 0 + +# environments that have tab delimiters, add more +# as needed +lookForAlignDelims: + tabular: 1 + tabularx: 1 + array: 1 + matrix: 1 + bmatrix: 1 + pmatrix: 1 + align: 1 + align*: 1 + alignat: 1 + alignat*: 1 + aligned: 1 + cases: 1 + dcases: 1 + pmatrix: 1 + listabla: 1 + +# if you have indent rules for particular environments +# or commands, put them in here; for example, you might just want +# to use a space " " or maybe a double tab "\t\t" +indentRules: + myenvironment: "\t\t" + anotherenvironment: "\t\t\t\t" + chapter: " " + section: " " + +# verbatim environments- environments specified +# in this hash table will not be changed at all! +verbatimEnvironments: + verbatim: 1 + lstlisting: 1 + +# no indent blocks (not necessarily verbatim +# environments) which are marked as %\begin{noindent} +# or anything else that the user puts in this hash +# table +noIndentBlock: + noindent: 1 + cmhtest: 1 + +# if you don't want to have additional indentation +# in an environment put it in this hash table; note that +# environments in this hash table will inherit +# the *current* level of indentation they just won't +# get any *additional*. +noAdditionalIndent: + myexample: 1 + mydefinition: 1 + problem: 1 + exercises: 1 + mysolution: 1 + foreach: 0 + widepage: 1 + comment: 1 + \[: 0 + \]: 0 + document: 1 + frame: 0 + +# if you want to add indentation after +# a heading, such as \part, \chapter, etc +# then populate it in here - you can add +# an indent rule to indentRules if you would +# like something other than defaultIndent +# +# you can also change the level if you like, +# or add your own title command +indentAfterHeadings: + part: + indent: 0 + level: 1 + chapter: + indent: 0 + level: 2 + section: + indent: 0 + level: 3 + subsection: + indent: 0 + level: 4 + subsection*: + indent: 0 + level: 4 + subsubsection: + indent: 0 + level: 5 + paragraph: + indent: 0 + level: 6 + subparagraph: + indent: 0 + level: 7 + +# *** NOTE *** +# If you have specified alwaysLookforSplitBraces: 1 +# and alwaysLookforSplitBrackets: 1 then you don't need +# to worry about completing +# +# checkunmatched +# checkunmatchedELSE +# checkunmatchedbracket +# +# in other words, you don't really need to edit anything +# below this line- it used to be necessary for older +# versions of the script, but not anymore :) +#*** *** + +# commands that might split {} across lines +# such as \parbox, \marginpar, etc +checkunmatched: + parbox: 1 + vbox: 1 + +# very similar to %checkunmatched except these +# commands might have an else construct +checkunmatchedELSE: + pgfkeysifdefined: 1 + DTLforeach: 1 + ifthenelse: 1 + +# commands that might split [] across lines +# such as \pgfplotstablecreatecol, etc +checkunmatchedbracket: + pgfplotstablecreatecol: 1 + pgfplotstablesave: 1 + pgfplotstabletypeset: 1 + mycommand: 1 + psSolid: 1 + diff --git a/Master/texmf-dist/scripts/latexindent/latexindent.pl b/Master/texmf-dist/scripts/latexindent/latexindent.pl new file mode 100755 index 00000000000..74ab7ebb3e5 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/latexindent.pl @@ -0,0 +1,1784 @@ +#!/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. +# +# See http://www.gnu.org/licenses/. +# +# For details of how to use this file, please see readme.txt + +# load packages/modules +use strict; +use warnings; +use FindBin; # help find defaultSettings.yaml +use YAML::Tiny; # interpret defaultSettings.yaml +use File::Copy; # to copy the original file to backup (if overwrite option set) +use File::Basename; # to get the filename and directory path +#use Getopt::Std; +use Getopt::Long; # to get the switches/options/flags +use File::HomeDir; # to get users home directory, regardless of OS + +# get the options +my $overwrite; +my $outputToFile; +my $silentMode; +my $tracingMode; +my $readLocalSettings; +my $onlyDefault; +my $showhelp; +my $cruftDirectory='./'; + +GetOptions ("w"=>\$overwrite, +"o"=>\$outputToFile, +"s"=>\$silentMode, +"t"=>\$tracingMode, +"l"=>\$readLocalSettings, +"d"=>\$onlyDefault, +"h"=>\$showhelp, +"c=s"=>\$cruftDirectory, +); + +die "Could not find directory $cruftDirectory\nExiting, no indentation done." if(!(-d $cruftDirectory)); + +# version number +my $versionNumber = "1.1R"; + +# Check the number of input arguments- if it is 0 then simply +# display the list of options (like a manual) +if(scalar(@ARGV) < 1 or $showhelp) +{ + print <","$cruftDirectory/indent.log") or die "Can't open indent.log"; + +# output time to log file +my $time = localtime(); +print $logfile $time; + +# output version to log file +print $logfile <2) +{ + for my $fh ($out,$logfile) {print $fh <new; + +print $logfile "Reading defaultSettings.yaml from $FindBin::Bin/defaultSettings.yaml\n\n"; + +# Open defaultSettings.yaml +$defaultSettings = YAML::Tiny->read( "$FindBin::Bin/defaultSettings.yaml" ); + +if(!$defaultSettings) +{ + for my $fh ($out,$logfile) { + print $fh <[0]->{defaultIndent}; +my $alwaysLookforSplitBraces = $defaultSettings->[0]->{alwaysLookforSplitBraces}; +my $alwaysLookforSplitBrackets = $defaultSettings->[0]->{alwaysLookforSplitBrackets}; +my $backupExtension = $defaultSettings->[0]->{backupExtension}; +my $indentPreamble = $defaultSettings->[0]->{indentPreamble}; +my $onlyOneBackUp = $defaultSettings->[0]->{onlyOneBackUp}; +my $maxNumberOfBackUps = $defaultSettings->[0]->{maxNumberOfBackUps}; +my $removeTrailingWhitespace = $defaultSettings->[0]->{removeTrailingWhitespace}; + +# hash variables +my %lookForAlignDelims= %{$defaultSettings->[0]->{lookForAlignDelims}}; +my %indentRules= %{$defaultSettings->[0]->{indentRules}}; +my %verbatimEnvironments= %{$defaultSettings->[0]->{verbatimEnvironments}}; +my %noIndentBlock= %{$defaultSettings->[0]->{noIndentBlock}}; +my %checkunmatched= %{$defaultSettings->[0]->{checkunmatched}}; +my %checkunmatchedELSE= %{$defaultSettings->[0]->{checkunmatchedELSE}}; +my %checkunmatchedbracket= %{$defaultSettings->[0]->{checkunmatchedbracket}}; +my %noAdditionalIndent= %{$defaultSettings->[0]->{noAdditionalIndent}}; +my %indentAfterHeadings= %{$defaultSettings->[0]->{indentAfterHeadings}}; + +# need new hashes to store the user and local data before +# overwriting the default +my %lookForAlignDelimsUSER; +my %indentRulesUSER; +my %verbatimEnvironmentsUSER; +my %noIndentBlockUSER; +my %checkunmatchedUSER; +my %checkunmatchedELSEUSER; +my %checkunmatchedbracketUSER; +my %noAdditionalIndentUSER; +my %indentAfterHeadingsUSER; + +# for printing the user and local settings to the log file +my %dataDump; + +# empty array to store the paths +my @absPaths; + +# scalar to read user settings +my $userSettings; + +# get information about user settings- first check if indentconfig.yaml exists +my $indentconfig = File::HomeDir->my_home . "/indentconfig.yaml"; +if ( -e $indentconfig and !$onlyDefault ) +{ + print $logfile "Reading path information from ",File::HomeDir->my_home,"/indentconfig.yaml\n"; + + # read the absolute paths from indentconfig.yaml + $userSettings = YAML::Tiny->read( "$indentconfig" ); + + # integrity check + if($userSettings) + { + %dataDump = %{$userSettings->[0]}; + print $logfile Dump \%dataDump; + print $logfile "\n"; + @absPaths = @{$userSettings->[0]->{paths}}; + } + else + { + print $logfile <my_home,"\n"; + print $logfile "To specify user settings you would put indentconfig.yaml here: \n\t",File::HomeDir->my_home,"/indentconfig.yaml\n\n"; + } +} + +# get information about LOCAL settings, assuming that localSettings.yaml exists +my $directoryName = dirname $ARGV[0]; + +# add local settings to the paths, if appropriate +if ( (-e "$directoryName/localSettings.yaml") and $readLocalSettings and !(-z "$directoryName/localSettings.yaml")) +{ + print $logfile "\nAdding ./localSettings.yaml to paths\n\n"; + push(@absPaths,"$directoryName/localSettings.yaml"); +} +elsif ( !(-e "$directoryName/localSettings.yaml") and $readLocalSettings) +{ + print $logfile "WARNING\n\t$directoryName/localSettings.yaml not found\n"; + print $logfile "\tcarrying on without it.\n"; +} + +# read in the settings from each file +foreach my $settings (@absPaths) +{ + # check that the settings file exists and that it isn't empty + if (-e $settings and !(-z $settings)) + { + print $logfile "Reading USER settings from $settings\n"; + $userSettings = YAML::Tiny->read( "$settings" ); + + # if we can read userSettings + if($userSettings) + { + # output settings to $logfile + %dataDump = %{$userSettings->[0]}; + print $logfile Dump \%dataDump; + print $logfile "\n"; + + # scalar variables + $defaultIndent = $userSettings->[0]->{defaultIndent} if defined($userSettings->[0]->{defaultIndent}); + $alwaysLookforSplitBraces = $userSettings->[0]->{alwaysLookforSplitBraces} if defined($userSettings->[0]->{alwaysLookforSplitBraces}); + $alwaysLookforSplitBrackets = $userSettings->[0]->{alwaysLookforSplitBrackets} if defined($userSettings->[0]->{alwaysLookforSplitBrackets}); + $backupExtension = $userSettings->[0]->{backupExtension} if defined($userSettings->[0]->{backupExtension}); + $indentPreamble = $userSettings->[0]->{indentPreamble} if defined($userSettings->[0]->{indentPreamble}); + $onlyOneBackUp = $userSettings->[0]->{onlyOneBackUp} if defined($userSettings->[0]->{onlyOneBackUp}); + $maxNumberOfBackUps = $userSettings->[0]->{maxNumberOfBackUps} if defined($userSettings->[0]->{maxNumberOfBackUps}); + $removeTrailingWhitespace = $userSettings->[0]->{removeTrailingWhitespace} if defined($userSettings->[0]->{removeTrailingWhitespace}); + + # hash variables - note that each one requires two lines, + # one to read in the data, one to put the keys&values in correctly + + %lookForAlignDelimsUSER= %{$userSettings->[0]->{lookForAlignDelims}} if defined($userSettings->[0]->{lookForAlignDelims}); + @lookForAlignDelims{ keys %lookForAlignDelimsUSER } = values %lookForAlignDelimsUSER if (%lookForAlignDelimsUSER); + + %indentRulesUSER= %{$userSettings->[0]->{indentRules}} if defined($userSettings->[0]->{indentRules}); + @indentRules{ keys %indentRulesUSER } = values %indentRulesUSER if (%indentRulesUSER); + + %verbatimEnvironmentsUSER= %{$userSettings->[0]->{verbatimEnvironments}} if defined($userSettings->[0]->{verbatimEnvironments}); + @verbatimEnvironments{ keys %verbatimEnvironmentsUSER } = values %verbatimEnvironmentsUSER if (%verbatimEnvironmentsUSER); + + %noIndentBlockUSER= %{$userSettings->[0]->{noIndentBlock}} if defined($userSettings->[0]->{noIndentBlock}); + @noIndentBlock{ keys %noIndentBlockUSER } = values %noIndentBlockUSER if (%noIndentBlockUSER); + + %checkunmatchedUSER= %{$userSettings->[0]->{checkunmatched}} if defined($userSettings->[0]->{checkunmatched}); + @checkunmatched{ keys %checkunmatchedUSER } = values %checkunmatchedUSER if (%checkunmatchedUSER); + + %checkunmatchedbracketUSER= %{$userSettings->[0]->{checkunmatchedbracket}} if defined($userSettings->[0]->{checkunmatchedbracket}); + @checkunmatchedbracket{ keys %checkunmatchedbracketUSER } = values %checkunmatchedbracketUSER if (%checkunmatchedbracketUSER); + + %noAdditionalIndentUSER= %{$userSettings->[0]->{noAdditionalIndent}} if defined($userSettings->[0]->{noAdditionalIndent}); + @noAdditionalIndent{ keys %noAdditionalIndentUSER } = values %noAdditionalIndentUSER if (%noAdditionalIndentUSER); + + %indentAfterHeadingsUSER= %{$userSettings->[0]->{indentAfterHeadings}} if defined($userSettings->[0]->{indentAfterHeadings}); + @indentAfterHeadings{ keys %indentAfterHeadingsUSER } = values %indentAfterHeadingsUSER if (%indentAfterHeadingsUSER); + + } + else + { + # otherwise print a warning that we can not read userSettings.yaml + print $logfile "WARNING\n\t$settings \n\t contains invalid yaml format- not reading from it\n"; + } + + } + else + { + # otherwise keep going, but put a warning in the log file + print $logfile "\nWARNING\n\t",File::HomeDir->my_home,"/indentconfig.yaml\n"; + if (-z $settings) + { + print $logfile "\tspecifies $settings \n\tbut this file is EMPTY- not reading from it\n\n" + } + else + { + print $logfile "\tspecifies $settings \n\tbut this file does not exist- unable to read settings from this file\n\n" + } + } +} + + +# if we want to over write the current file +# create a backup first +if ($overwrite) +{ + print $logfile "\nBackup procedure:\n"; + # original name of file + my $filename = $ARGV[0]; + # copy it + my $backupFile = $filename; + + # add the user's backup directory to the backup path + $backupFile = "$cruftDirectory/$backupFile"; + + # if both ($onlyOneBackUp and $maxNumberOfBackUps) then we have + # a conflict- er on the side of caution and turn off onlyOneBackUp + if($onlyOneBackUp and $maxNumberOfBackUps>1) + { + print $logfile "\t WARNING: onlyOneBackUp=$onlyOneBackUp and maxNumberOfBackUps: $maxNumberOfBackUps\n"; + print $logfile "\t\t setting onlyOneBackUp=0 which will allow you to reach $maxNumberOfBackUps back ups\n"; + $onlyOneBackUp = 0; + } + + # if the user has specified that $maxNumberOfBackUps = 1 then + # they only want one backup + if($maxNumberOfBackUps==1) + { + $onlyOneBackUp=1 ; + print $logfile "\t FYI: you set maxNumberOfBackUps=1, so I'm setting onlyOneBackUp: 1 \n"; + } + elsif($maxNumberOfBackUps<=0 and !$onlyOneBackUp) + { +# print $logfile "\t FYI: maxNumberOfBackUps=$maxNumberOfBackUps which won't have any effect\n"; +# print $logfile "\t on the script- at least ONE backup is made when the -w flag is invoked.\n"; +# print $logfile "\t I'm setting onlyOneBackUp: 0, which means that you'll get a new back up file \n"; +# print $logfile "\t every time you run the script.\n"; + $onlyOneBackUp=0 ; + $maxNumberOfBackUps=-1; + } + + # if onlyOneBackUp is set, then the backup file will + # be overwritten each time + if($onlyOneBackUp) + { + $backupFile =~ s/\.tex/$backupExtension/; + print $logfile "\t copying $filename to $backupFile\n"; + print $logfile "\t $backupFile was overwritten\n\n" if (-e $backupFile); + } + else + { + # start with a backup file .bak0 (or whatever $backupExtension is present) + my $backupCounter = 0; + $backupFile =~ s/\.tex$/$backupExtension$backupCounter/; + + # if it exists, then keep going: .bak0, .bak1, ... + while (-e $backupFile or $maxNumberOfBackUps>1) + { + if($backupCounter==$maxNumberOfBackUps) + { + print $logfile "\t maxNumberOfBackUps reached ($maxNumberOfBackUps)\n"; + $maxNumberOfBackUps=1 ; + last; # break out of the loop + } + elsif(!(-e $backupFile)) + { + $maxNumberOfBackUps=1 ; + last; # break out of the loop + } + print $logfile "\t $backupFile already exists, incrementing by 1...\n"; + $backupCounter++; + $backupFile =~ s/$backupExtension.*/$backupExtension$backupCounter/; + } + print $logfile "\n\t copying $filename to $backupFile\n\n"; + } + + # output these lines to the log file + print $logfile "\t Backup file: ",$backupFile,"\n"; + print $logfile "\t Overwriting file: ",$filename,"\n\n"; + copy($filename,$backupFile) or die "Could not write to backup file $backupFile. Please check permissions. Exiting.\n"; +} + +if(!($outputToFile or $overwrite)) +{ + print $logfile "Just out put to the terminal :)\n\n" if !$silentMode ; +} + + +# scalar variables +my $line; # $line: takes the $line of the file +my $inpreamble=!$indentPreamble; + # $inpreamble: switch to determine if in + # preamble or not +my $inverbatim=0; # $inverbatim: switch to determine if in + # a verbatim environment or not +my $delimiters=0; # $delimiters: switch that governs if + # we need to check for & or not +my $matchedbraces=0; # $matchedbraces: counter to see if { } + # are matched; it will be + # positive if too many { + # negative if too many } + # 0 if matched +my $matchedBRACKETS=0; # $matchedBRACKETS: counter to see if [ ] + # are matched; it will be + # positive if too many { + # negative if too many } + # 0 if matched +my $commandname; # $commandname: either \parbox, \marginpar, + # or anything else from %checkunmatched +my $commanddetails; # $command details: a scalar that stores + # details about the command + # that splits {} across lines +my $countzeros; # $countzeros: a counter that helps + # when determining if we're in + # an else construct +my $lookforelse=0; # $lookforelse: a boolean to help determine + # if we need to look for an + # else construct +my $trailingcomments; # $trailingcomments stores the comments at the end of + # a line +my $lineCounter=0; # $lineCounter keeps track of the line number +my $inIndentBlock=0; # $inindentblock: switch to determine if in + # a inindentblock or not +my $headingLevel=0; # $headingLevel: scalar that stores which heading + # we are under: \part, \chapter, etc + +# array variables +my @indent; # @indent: stores current level of indentation +my @lines; # @lines: stores the newly indented lines +my @block; # @block: stores blocks that have & delimiters +my @commandstore; # @commandstore: stores commands that + # have split {} across lines +my @commandstorebrackets; # @commandstorebrackets: stores commands that + # have split [] across lines +my @mainfile; # @mainfile: stores input file; used to + # grep for \documentclass +my @headingStore; # @headingStore: stores headings: chapter, section, etc +my @indentNames; # @indentNames: keeps names of commands and + # environments that have caused + # indentation to increase +my @environmentStack; # @environmentStack: stores the (nested) names + # of environments + +# check to see if the current file has \documentclass, if so, then +# it's the main file, if not, then it doesn't have preamble +open(MAINFILE, $ARGV[0]) or die "Could not open input file"; + @mainfile=; +close(MAINFILE); + +# if the MAINFILE doesn't have a \documentclass statement, then +# it shouldn't have preamble +if(scalar(@{[grep(m/^\s*\\documentclass/, @mainfile)]})==0) +{ + $inpreamble=0; + + print $logfile "Trace:\tNo documentclass detected, assuming no preamble\n" if($tracingMode); +} +else +{ + print $logfile "Trace:\t documentclass detected, assuming preamble\n" if($tracingMode); +} + +# the previous OPEN command puts us at the END of the file +open(MAINFILE, $ARGV[0]) or die "Could not open input file"; + +# loop through the lines in the INPUT file +while() +{ + # increment the line counter + $lineCounter++; + + # tracing mode + print $logfile "\n" if($tracingMode and !($inpreamble or $inverbatim or $inIndentBlock)); + + # check to see if we're still in the preamble + # or in a verbatim environment or in IndentBlock + if(!($inpreamble or $inverbatim or $inIndentBlock)) + { + # if not, remove all leading spaces and tabs + # from the current line, assuming it isn't empty + #s/^\ *//; + #s/^\s+//; + #s/^\t+//; + s/^\t*// if($_ !~ /^((\s*)|(\t*))*$/); + s/^\s*// if($_ !~ /^((\s*)|(\t*))*$/); + + # tracing mode + print $logfile "Line $lineCounter\t removing leading spaces\n" if($tracingMode); + } + else + { + # otherwise check to see if we've reached the main + # part of the document + if(m/^\s*\\begin{document}/) + { + $inpreamble = 0; + + # tracing mode + print $logfile "Line $lineCounter\t \\begin{document} found \n" if($tracingMode); + } + else + { + # tracing mode + if($inpreamble) + { + print $logfile "Line $lineCounter\t still in PREAMBLE, doing nothing\n" if($tracingMode); + } + elsif($inverbatim) + { + print $logfile "Line $lineCounter\t in VERBATIM-LIKE environment, doing nothing\n" if($tracingMode); + } + elsif($inIndentBlock) + { + print $logfile "Line $lineCounter\t in NO INDENT BLOCK, doing nothing\n" if($tracingMode); + } + } + } + + # check to see if we have \end{something} or \] + &at_end_of_env_or_eq() unless ($inpreamble or $inIndentBlock); + + # check to see if we're at the end of a noindent + # block %\end{noindent} + &at_end_noindent(); + + # only check for unmatched braces if we're not in + # a verbatim-like environment or in the preamble or in a + # noIndentBlock + if(!($inverbatim or $inpreamble or $inIndentBlock)) + { + # The check for closing } and ] relies on counting, so + # we have to remove trailing comments so that any {, }, [, ] + # that are found after % are not counted + # + # note that these lines are NOT in @lines, so we + # have to store the $trailingcomments to put + # back on after the counting + # + # note the use of (?",$ARGV[0]); + print OUTPUTFILE @lines; + close(OUTPUTFILE); +} + +# if -o is active then output to $ARGV[1] +if($outputToFile) +{ + open(OUTPUTFILE,">",$ARGV[1]); + print OUTPUTFILE @lines; + close(OUTPUTFILE); +} + +# close the log file +close($logfile); + +exit; + +sub indent_heading{ + # PURPOSE: This matches + # \part + # \chapter + # \section + # \subsection + # \subsubsection + # \paragraph + # \subparagraph + # + # and anything else listed in indentAfterHeadings + # + # This subroutine specifies the indentation for the + # heading itself, i.e the line that has \chapter, \section etc + if( $_ =~ m/^\s*\\(.*?)(\[|{)/ and $indentAfterHeadings{$1}) + { + # tracing mode + print $logfile "Line $lineCounter\t Heading found: $1 \n" if($tracingMode); + + # get the heading settings- it's a hash within a hash + my %currentHeading = %{$indentAfterHeadings{$1}}; + + # local scalar + my $previousHeadingLevel = $headingLevel; + + # if current heading level < old heading level, + if($currentHeading{level}<$previousHeadingLevel) + { + # decrease indentation, but only if + # specified in indentHeadings. Note that this check + # needs to be done here- decrease_indent won't + # check a nested hash + + if(scalar(@headingStore)) + { + while($currentHeading{level}<$previousHeadingLevel and scalar(@headingStore)) + { + my $higherHeadingName = pop(@headingStore); + my %higherLevelHeading = %{$indentAfterHeadings{$higherHeadingName}}; + + # tracing mode + print $logfile "Line $lineCounter\t stepping UP heading level from $higherHeadingName \n" if($tracingMode); + + &decrease_indent($higherHeadingName) if($higherLevelHeading{indent}); + $previousHeadingLevel=$higherLevelHeading{level}; + } + # put the heading name back in to storage + push(@headingStore,$1); + } + } + elsif($currentHeading{level}==$previousHeadingLevel) + { + if(scalar(@headingStore)) + { + my $higherHeadingName = pop(@headingStore); + my %higherLevelHeading = %{$indentAfterHeadings{$higherHeadingName}}; + &decrease_indent($higherHeadingName) if($higherLevelHeading{indent}); + } + # put the heading name back in to storage + push(@headingStore,$1); + } + else + { + # put the heading name into storage + push(@headingStore,$1); + } + } +} + +sub indent_after_heading{ + # PURPOSE: This matches + # \part + # \chapter + # \section + # \subsection + # \subsubsection + # \paragraph + # \subparagraph + # + # and anything else listed in indentAfterHeadings + # + # This subroutine is specifies the indentation for + # the text AFTER the heading, i.e the body of conent + # in each \chapter, \section, etc + if( $_ =~ m/^\s*\\(.*?)(\[|{)/ and $indentAfterHeadings{$1}) + { + # get the heading settings- it's a hash within a hash + my %currentHeading = %{$indentAfterHeadings{$1}}; + + &increase_indent($1) if($currentHeading{indent}); + + # update heading level + $headingLevel = $currentHeading{level}; + } +} + + + +sub at_end_noindent{ + # PURPOSE: This matches + # % \end{noindent} + # with any number of spaces (possibly none) between + # the comment and \end{noindent}. + # + # the comment symbol IS indended! + # + # This is for blocks of code that the user wants + # to leave untouched- similar to verbatim blocks + + if( $_ =~ m/^%\s*\\end{(.*?)}/ and $noIndentBlock{$1}) + { + $inIndentBlock=0; + # tracing mode + print $logfile "Line $lineCounter\t % \\end{no indent block} found, switching inIndentBlock OFF \n" if($tracingMode); + } +} + +sub at_beg_noindent{ + # PURPOSE: This matches + # % \begin{noindent} + # with any number of spaces (possibly none) between + # the comment and \begin{noindent}. + # + # the comment symbol IS indended! + # + # This is for blocks of code that the user wants + # to leave untouched- similar to verbatim blocks + + if( $_ =~ m/^%\s*\\begin{(.*?)}/ and $noIndentBlock{$1}) + { + $inIndentBlock = 1; + # tracing mode + print $logfile "Line $lineCounter\t % \\begin{no indent block} found, switching inIndentBlock ON \n" if($tracingMode); + } +} + +sub start_command_or_key_unmatched_brackets{ + # PURPOSE: This matches + # \pgfplotstablecreatecol[... + # + # or any other command/key that has brackets [ ] + # split across lines specified in the + # hash tables, %checkunmatchedbracket + # + # How to read: ^\s*(\\)?(.*?)(\[\s*) + # + # ^ line begins with + # \s* any (or no)spaces + # (\\)? matches a \ backslash but not necessarily + # (.*?) non-greedy character match and store the result + # ((?$commandname, + matchedBRACKETS=>$matchedBRACKETS}); + + } + } +} + +sub end_command_or_key_unmatched_brackets{ + # PURPOSE: Check for the closing BRACKET of a command that + # splits its BRACKETS across lines, such as + # + # \pgfplotstablecreatecol[ ... + # + # It works by checking if we have any entries + # in the array @commandstorebrackets, and making + # sure that we're not starting another command/key + # that has split BRACKETS (nesting). + # + # It also checks that the line is not commented. + # + # We count the number of [ and ADD to the counter + # ] and SUBTRACT to the counter + if(scalar(@commandstorebrackets) + and !($_ =~ m/^\s*(\\)?(.*?)(\s*\[)/ + and (scalar($checkunmatchedbracket{$2}) + or $alwaysLookforSplitBrackets)) + and $_ !~ m/^\s*%/ + ) + { + # get the details of the most recent command name + $commanddetails = pop(@commandstorebrackets); + $commandname = $commanddetails->{'commandname'}; + $matchedBRACKETS = $commanddetails->{'matchedBRACKETS'}; + + # match [ but don't match \[ + $matchedBRACKETS++ while ($_ =~ m/(?$commandname, + matchedBRACKETS=>$matchedBRACKETS}); + # tracing mode + print $logfile "Line $lineCounter\t Searching for closing BRACKET ] $commandname\n" if($tracingMode); + } + } +} + +sub start_command_or_key_unmatched_braces{ + # PURPOSE: This matches + # \parbox{... + # \parbox[..]..{ + # empty header/.style={ + # \foreach \something + # etc + # + # or any other command/key that has BRACES + # split across lines specified in the + # hash tables, %checkunmatched, %checkunmatchedELSE + # + # How to read: ^\s*(\\)?(.*?)(\[|{|\s) + # + # ^ line begins with + # \s* any (or no) spaces + # (\\)? matches a \ backslash but not necessarily + # (.*?) non-greedy character match and store the result + # (\[|}|=|(\s*\\)) either [ or { or = or space \ + + if ($_ =~ m/^\s*(\\)?(.*?)(\[|{|=|(\s*\\))/ + and (scalar($checkunmatched{$2}) + or scalar($checkunmatchedELSE{$2}) + or $alwaysLookforSplitBraces) + ) + { + # store the command name, because $2 + # will not exist after the next match + $commandname = $2; + $matchedbraces=0; + + # by default, don't look for an else construct + $lookforelse=0; + if(scalar($checkunmatchedELSE{$2})) + { + $lookforelse=1; + } + + # match { but don't match \{ + $matchedbraces++ while ($_ =~ /(? 0 ) + { + # tracing mode + print $logfile "Line $lineCounter\t Found opening BRACE { $commandname\n" if($tracingMode); + + &increase_indent($commandname); + + # store the command name + # and the value of $matchedbraces + push(@commandstore,{commandname=>$commandname, + matchedbraces=>$matchedbraces, + lookforelse=>$lookforelse, + countzeros=>0}); + + } + elsif($matchedbraces<0) + { + # if $matchedbraces < 0 then we must be matching + # braces from a previous split-braces command + + # keep matching { OR }, and don't match \{ or \} + while ($_ =~ m/(((?{'commandname'}; + $matchedbraces = $commanddetails->{'matchedbraces'}; + $countzeros = $commanddetails->{'countzeros'}; + $lookforelse= $commanddetails->{'lookforelse'}; + + $matchedbraces++ if($1 eq "{"); + $matchedbraces-- if($1 eq "}"); + + # if we've matched up the braces then + # we can decrease the indent by 1 level + if($matchedbraces == 0) + { + $countzeros++ if $lookforelse; + + # tracing mode + print $logfile "Line $lineCounter\t Found closing BRACE } $1\n" if($tracingMode); + + # decrease the indentation (if appropriate) + &decrease_indent($commandname); + + if($countzeros==1) + { + push(@commandstore,{commandname=>$commandname, + matchedbraces=>$matchedbraces, + lookforelse=>$lookforelse, + countzeros=>$countzeros}); + } + } + else + { + # otherwise we need to put the command back for the + # next brace count + push(@commandstore,{commandname=>$commandname, + matchedbraces=>$matchedbraces, + lookforelse=>$lookforelse, + countzeros=>$countzeros}); + } + } + } + } +} + +sub end_command_or_key_unmatched_braces{ + # PURPOSE: Check for the closing BRACE of a command that + # splits its BRACES across lines, such as + # + # \parbox{ ... + # + # or one of the tikz keys, such as + # + # empty header/.style={ + # + # It works by checking if we have any entries + # in the array @commandstore, and making + # sure that we're not starting another command/key + # that has split BRACES (nesting). + # + # It also checks that the line is not commented. + # + # We count the number of { and ADD to the counter + # } and SUBTRACT to the counter + if(scalar(@commandstore) + and !($_ =~ m/^\s*(\\)?(.*?)(\[|{|=|(\s*\\))/ + and (scalar($checkunmatched{$2}) + or scalar($checkunmatchedELSE{$2}) + or $alwaysLookforSplitBraces)) + and $_ !~ m/^\s*%/ + ) + { + # keep matching { OR }, and don't match \{ or \} + while ($_ =~ m/(((?{'commandname'}; + $matchedbraces = $commanddetails->{'matchedbraces'}; + $countzeros = $commanddetails->{'countzeros'}; + $lookforelse= $commanddetails->{'lookforelse'}; + + $matchedbraces++ if($1 eq "{"); + $matchedbraces-- if($1 eq "}"); + + # if we've matched up the braces then + # we can decrease the indent by 1 level + if($matchedbraces == 0) + { + $countzeros++ if $lookforelse; + + # tracing mode + print $logfile "Line $lineCounter\t Found closing BRACE } $commandname\n" if($tracingMode); + + # decrease the indentation (if appropriate) + &decrease_indent($commandname); + + if($countzeros==1) + { + push(@commandstore,{commandname=>$commandname, + matchedbraces=>$matchedbraces, + lookforelse=>$lookforelse, + countzeros=>$countzeros}); + } + } + else + { + # otherwise we need to enter the new value + # of $matchedbraces and the value of $command + # back into storage + push(@commandstore,{commandname=>$commandname, + matchedbraces=>$matchedbraces, + lookforelse=>$lookforelse, + countzeros=>$countzeros}); + + # tracing mode + print $logfile "Line $lineCounter\t Searching for closing BRACE } $commandname\n" if($tracingMode); + } + } + } +} + +sub check_for_else{ + # PURPOSE: Check for an else clause + # + # Some commands have the form + # + # \mycommand{ + # if this + # } + # { + # else this + # } + # + # so we need to look for the else bit, and set + # the indentation appropriately. + # + # We only perform this check if there's something + # in the array @commandstore, and if + # the line itself is not a command, or comment, + # and if it begins with { + + if(scalar(@commandstore) + and !($_ =~ m/^\s*(\\)?(.*?)(\[|{|=)/ + and (scalar($checkunmatched{$2}) + or scalar($checkunmatchedELSE{$2}) + or $alwaysLookforSplitBraces)) + and $_ =~ m/^\s*{/ + and $_ !~ m/^\s*%/ + ) + { + # get the details of the most recent command name + $commanddetails = pop(@commandstore); + $commandname = $commanddetails->{'commandname'}; + $matchedbraces = $commanddetails->{'matchedbraces'}; + $countzeros = $commanddetails->{'countzeros'}; + $lookforelse= $commanddetails->{'lookforelse'}; + + # increase indentation + if($lookforelse and $countzeros==1) + { + &increase_indent($commandname); + } + + # put the array back together + push(@commandstore,{commandname=>$commandname, + matchedbraces=>$matchedbraces, + lookforelse=>$lookforelse, + countzeros=>$countzeros}); + } +} + +sub at_beg_of_env_or_eq{ + # PURPOSE: Check if we're at the BEGINning of an environment + # or at the BEGINning of a displayed equation \[ + # + # This subroutine checks for matches of the form + # + # \begin{environmentname} + # or + # \[ + # + # It also checks to see if the current environment + # should have alignment delimiters; if so, we need to turn + # ON the $delimiter switch + + # How to read + # m/^\s*(\$)?\\begin{(.*?)}/ + # + # ^ beginning of a line + # \s* any white spaces (possibly none) + # (\$)? possibly a $ symbol, but not required + # \\begin{(.*)?} \begin{environmentname} + # + # How to read + # m/^\s*()(\\\[)/ + # + # ^ beginning of a line + # \s* any white spaces (possibly none) + # () empty just so that $1 and $2 are defined + # (\\\[) \[ there are lots of \ because both \ and [ need escaping + + if( ( ( $_ =~ m/^\s*(\$)?\\begin{(.*?)}/ and $_ !~ m/\\end{$2}/) + or ($_=~ m/^\s*()(\\\[)/ and $_ !~ m/\\\]/) ) + and $_ !~ m/^\s*%/ ) + { + # tracing mode + print $logfile "Line $lineCounter\t \\begin{environment} found: $2 \n" if($tracingMode); + + # increase the indentation + &increase_indent($2); + + # check for verbatim-like environments + if($verbatimEnvironments{$2}) + { + $inverbatim = 1; + # tracing mode + print $logfile "Line $lineCounter\t \\begin{verbatim-like} found, $2, switching ON verbatim \n" if($tracingMode); + + # remove the key and value from %lookForAlignDelims hash + # to avoid any further confusion + if($lookForAlignDelims{$2}) + { + print $logfile "WARNING\n\t Line $lineCounter\t $2 is in *both* lookForAlignDelims and verbatimEnvironments\n"; + print $logfile "\t\t\t ignoring lookForAlignDelims and prioritizing verbatimEnvironments\n"; + print $logfile "\t\t\t Note that you only get this message once per environment\n"; + delete $lookForAlignDelims{$2}; + } + + } + + # check to see if we need to look for alignment + # delimiters + if($lookForAlignDelims{$2}) + { + $delimiters=1; + # tracing mode + print $logfile "Line $lineCounter\t Delimiter environment started: $2 (see lookForAlignDelims)\n" if($tracingMode); + } + + # store the name of the environment + push(@environmentStack,$2); + + } +} + +sub at_end_of_env_or_eq{ + # PURPOSE: Check if we're at the END of an environment + # or at the END of a displayed equation \] + # + # This subroutine checks for matches of the form + # + # \end{environmentname} + # or + # \] + # + # It also checks to see if the current environment + # had alignment delimiters; if so, we need to turn + # OFF the $delimiter switch + + if( ($_ =~ m/^\s*\\end{(.*?)}/ or $_=~ m/^(\\\])/) + and $_ !~ m/\s*^%/) + { + + # check if we're at the end of a verbatim-like environment + if($verbatimEnvironments{$1}) + { + $inverbatim = 0; + # tracing mode + + print $logfile "Line $lineCounter\t \\end{verbatim-like} found: $1, switching off verbatim \n" if($tracingMode); + print $logfile "Line $lineCounter\t removing leading spaces \n" if($tracingMode); + #s/^\ *//; + s/^\t+// if($_ ne ""); + s/^\s+// if($_ ne ""); + } + + # check to see if \end{environment} fits with most recent \begin{...} + my $previousEnvironment = pop(@environmentStack); + + # check to see if we need to turn off alignment + # delimiters and output the current block + if($lookForAlignDelims{$1} and ($previousEnvironment eq $1)) + { + $delimiters=0; + + # tracing mode + print $logfile "Line $lineCounter\t Delimiter body FINISHED: $1\n" if($tracingMode); + + # print the current FORMATTED block + @block = &format_block(@block); + foreach $line (@block) + { + # add the indentation and add the + # each line of the formatted block + # to the output + # unless this would only create trailing whitespace and the + # corresponding option is set + unless ($line =~ m/^$/ and $removeTrailingWhitespace) + { + $line = join("",@indent).$line; + } + push(@lines,$line); + } + # empty the @block, very important! + @block=(); + } + + # tracing mode + print $logfile "Line $lineCounter\t \\end{envrionment} found: $1 \n" if($tracingMode and !$verbatimEnvironments{$1}); + + # check to see if \end{environment} fits with most recent \begin{...} + if($previousEnvironment eq $1) + { + # decrease the indentation (if appropriate) + &decrease_indent($1); + } + else + { + # otherwise put the environment name back on the stack + push(@environmentStack,$previousEnvironment); + print $logfile "Line $lineCounter\t WARNING: \\end{$1} found on its own line, not matched to \\begin{$previousEnvironment}\n" unless ($delimiters or $inverbatim or $inIndentBlock or $1 eq "\\\]"); + } + + # need a special check for \[ and \] + if($1 eq "\\\]") + { + &decrease_indent($1); + pop(@environmentStack); + } + + # if we're at the end of the document, we remove all current + # indentation- this is especially prominent in examples that + # have headings, and the user has chosen to indentAfterHeadings + if($1 eq "document" and !(grep(/filecontents/, @indentNames)) and !$inpreamble and !$delimiters and !$inverbatim and !$inIndentBlock) + { + @indent=(); + @indentNames=(); + print $logfile "Line $lineCounter\t \\end{$1} found- emptying indent array \n" unless ($delimiters or $inverbatim or $inIndentBlock or $1 eq "\\\]"); + } + } +} + +sub format_block{ + # PURPOSE: Format a delimited environment such as the + # tabular or align environment that contains & + # + # INPUT: @block array containing unformatted block + # from, for example, align, or tabular + # OUTPUT: @formattedblock array containing FORMATTED block + + + # @block is the input + my @block=@_; + + # tracing mode + print $logfile "\t\tFormatting alignment block\n" if($tracingMode); + + # step the line counter back to the beginning of the block- + # it will be increased back to the end of the block in the + # loop later on: foreach $row (@tmpblock) + $lineCounter -= scalar(@block); + + + # local array variables + my @formattedblock; + my @tmprow=(); + my @tmpblock=(); + my @maxmstringsize=(); + my @ampersandCount=(); + + # local scalar variables + my $alignrowcounter=-1; + my $aligncolcounter=-1; + my $tmpstring; + my $row; + my $column; + my $maxmcolstrlength; + my $i; + my $j; + my $fmtstring; + my $linebreak; + my $maxNumberAmpersands = 0; + my $currentNumberAmpersands; + my $trailingcomments; + + # local hash table + my %stringsize=(); + + # loop through the block and count & per line- store the biggest + # NOTE: this needs to be done in its own block so that + # we can know what the maximum number of & in the block is + foreach $row (@block) + { + # delete trailing comments + $trailingcomments=''; + if($row =~ m/((? $maxNumberAmpersands ); + + # put trailing comments back on + if($trailingcomments) + { + $row =~ s/%TC/$trailingcomments/; + } + } + + # tracing mode + print $logfile "\t\tmaximum number of & in any row: $maxNumberAmpersands\n" if($tracingMode); + + # loop through the lines in the @block + foreach $row (@block) + { + # get the ampersand count + $currentNumberAmpersands = shift(@ampersandCount); + + # increment row counter + $alignrowcounter++; + + # clear the $linebreak variable + $linebreak=''; + + # check for line break \\ + # and don't mess with a line that doesn't have the maximum + # number of & + if($row =~ m/\\\\/ and $currentNumberAmpersands==$maxNumberAmpersands ) + { + # remove \\ and all characters that follow + # and put it back in later, once the measurement + # has been done + $row =~ s/(\\\\.*)//; + $linebreak = $1; + } + + if($currentNumberAmpersands==$maxNumberAmpersands) + { + + # remove trailing comments + $trailingcomments=''; + if($row =~ m/((?$maxmcolstrlength) + { + $maxmcolstrlength = $stringsize{$i.$j}; + } + } + } + push(@maxmstringsize,$maxmcolstrlength); + } + + # README: printf( formatting, expression) + # + # formatting has the form %-50s & %-20s & %-19s + # (the numbers have been made up for example) + # the - symbols mean that each column should be left-aligned + # the numbers represent how wide each column is + # the s represents string + # the & needs to be inserted + + # join up the maximum string lengths using "s %-" + $fmtstring = join("s & %-",@maxmstringsize); + + # add an s to the end, and a newline + $fmtstring .= "s "; + + # add %- to the beginning + $fmtstring = "%-".$fmtstring; + + # process the @tmpblock of aligned material + foreach $row (@tmpblock) + { + + $linebreak=''; + # check for line break \\ + if($row =~ m/\\\\/) + { + # remove \\ and all characters that follow + # and put it back in later + $row =~ s/(\\\\.*$)//; + $linebreak = $1; + } + + if($row =~ m/NOFORMATTING/) + { + $row =~ s/NOFORMATTING//; + $tmpstring=$row; + + # tracing mode + print $logfile "\t\tLine $lineCounter\t maximum number of & NOT found- not aligning delimiters \n" if($tracingMode); + } + else + { + # remove trailing comments + $trailingcomments=''; + if($row =~ m/((?