diff options
author | Karl Berry <karl@freefriends.org> | 2017-02-24 22:07:42 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2017-02-24 22:07:42 +0000 |
commit | 990e60c6ed4437e5b632b7c9acf7f2237bbda871 (patch) | |
tree | 4dc9ea04f5fa46cc8ac59faa8dd62e4f8bc6835b /Master/texmf-dist/scripts/latexindent | |
parent | 2e70942b44a972908c3180dd7a3c68be51afc466 (diff) |
latexindent (23feb17)
git-svn-id: svn://tug.org/texlive/trunk@43326 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/latexindent')
34 files changed, 5933 insertions, 2269 deletions
diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/AlignmentAtAmpersand.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/AlignmentAtAmpersand.pm new file mode 100644 index 00000000000..3f86cffd10f --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/AlignmentAtAmpersand.pm @@ -0,0 +1,255 @@ +package LatexIndent::AlignmentAtAmpersand; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Tokens qw/%tokens/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/align_at_ampersand find_aligned_block/; +our $alignmentBlockCounter; + +sub find_aligned_block{ + my $self = shift; + + return unless (${$self}{body} =~ m/(?!<\\)%\*\h*\\begin\{/s); + + # aligned block + # %* \begin{tabular} + # 1 & 2 & 3 & 4 \\ + # 5 & & 6 & \\ + # %* \end{tabular} + $self->logger('looking for ALIGNED blocks marked by comments','heading')if($is_t_switch_active); + $self->logger(Dumper(\%{$masterSettings{lookForAlignDelims}})) if($is_t_switch_active); + while( my ($alignmentBlock,$yesno)= each %{$masterSettings{lookForAlignDelims}}){ + if(ref $yesno eq "HASH"){ + $yesno = (defined ${$yesno}{delims} ) ? ${$yesno}{delims} : 1; + } + if($yesno){ + $self->logger("looking for $alignmentBlock:$yesno environments"); + + my $noIndentRegExp = qr/ + ( + (?!<\\) + % + \* + \h* # possible horizontal spaces + \\begin\{ + $alignmentBlock # environment name captured into $2 + \} # %* \begin{alignmentBlock} statement + ) + ( + .*? + ) + \R + \h* + ( + (?!<\\) + %\* # % + \h* # possible horizontal spaces + \\end\{$alignmentBlock\} # \end{alignmentBlock} + ) # %* \end{<something>} statement + #\R + /sx; + + while( ${$self}{body} =~ m/$noIndentRegExp/sx){ + + ${$self}{body} =~ s/ + $noIndentRegExp + / + # create a new Environment object + my $alignmentBlock = LatexIndent::AlignmentAtAmpersand->new( begin=>$1, + body=>$2, + end=>$3, + name=>$alignmentBlock, + modifyLineBreaksYamlName=>"environments", + linebreaksAtEnd=>{ + begin=>1, + body=>1, + end=>0, + }, + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($alignmentBlock); + ${@{${$self}{children}}[-1]}{replacementText}; + /xseg; + } + } else { + $self->logger("*not* looking for $alignmentBlock as $alignmentBlock:$yesno"); + } + } + return; +} + +sub modify_line_breaks_settings{ + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; +# $self->remove_leading_space; +} + +sub create_unique_id{ + my $self = shift; + + $alignmentBlockCounter++; + ${$self}{id} = "$tokens{alignmentBlock}$alignmentBlockCounter"; + return; +} + +sub align_at_ampersand{ + my $self = shift; + return if(${$self}{bodyLineBreaks}==0); + + # calculate the maximum number of ampersands in a row in the body + my $maximumNumberOfAmpersands = 0; + foreach(split("\n",${$self}{body})){ + my $numberOfAmpersands = () = $_ =~ /(?<!\\)&/g; + $maximumNumberOfAmpersands = $numberOfAmpersands if($numberOfAmpersands>$maximumNumberOfAmpersands); + } + + # create an array of zeros + my @columnSizes = (0) x ($maximumNumberOfAmpersands+1); + + # array for the new body + my @formattedBody; + + # now loop back through the body, and store the maximum column size + foreach(split("\n",${$self}{body})){ + # remove \\ and anything following it + my $endPiece; + if($_ =~ m/(\\\\.*)/){ + $_ =~ s/(\\\\.*)//; + $endPiece = $1; + } + + my $numberOfAmpersands = () = $_ =~ /(?<!\\)&/g; + if($numberOfAmpersands == $maximumNumberOfAmpersands){ + # remove any trailing comments + my $trailingComments; + if($_ =~ m/$trailingCommentRegExp/ ){ + $_ =~ s/($trailingCommentRegExp)//; + $trailingComments = $1; + } + + # if the line finishes with an &, then add an empty space, + # otherwise the column count is off + $_ .= ($_ =~ m/(?<!\\)&$/ ? " ":q()); + + # loop through the columns + my $columnCount = 0; + my $strippedRow = ''; + foreach my $column (split(/(?<!\\)&/,$_)){ + # remove leading space + $column =~ s/^\h*//; + + # remove trailing space + $column =~ s/\h*$//; + + # if a column has finished with a \ then we need to add a trailing space, + # otherwise the \ can be put next to &. See test-cases/texexchange/112343-gonzalo for example + $column .= ($column =~ m/\\$/ ? " ": q()); + + # store the column size + $columnSizes[$columnCount] = length($column) if(length($column)>$columnSizes[$columnCount]); + + # put the row back together, using " " if the column is empty + $strippedRow .= ($columnCount>0 ? "&" : q() ).(length($column)>0 ? $column: " "); + + # move on to the next column + $columnCount++; + } + + # store the information + push(@formattedBody,{ + row=>$strippedRow, + format=>1, + endPiece=>($endPiece ? $endPiece :q() ), + trailingComment=>($trailingComments ? $trailingComments :q() )}); + } else { + # otherwise simply store the row + push(@formattedBody,{ + row=>$_.($endPiece ? $endPiece : q() ), + format=>0}); + } + } + + # output some of the info so far to the log file + $self->logger("Column sizes of horizontally stripped formatted block (${$self}{name}): @columnSizes") if $is_t_switch_active; + + # 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 %-" + my $fmtstring = join("s & %-",@columnSizes); + + # add %- to the beginning and an s to the end + $fmtstring = "%-".$fmtstring."s "; + + # log file info + $self->logger("Formatting string is: $fmtstring",'heading') if $is_t_switch_active; + + # finally, reformat the body + foreach(@formattedBody){ + if(${$_}{format} and ${$_}{row} !~ m/^\h*$/){ + # format the row, and put the trailing \\ and trailing comments back into the row + ${$_}{row} = sprintf($fmtstring,split(/(?<!\\)&/,${$_}{row})).(${$_}{endPiece} ? ${$_}{endPiece} :q() ).(${$_}{trailingComment}? ${$_}{trailingComment} : q() ); + + # possibly remove space ahead of \\ + ${$_}{row} =~ s/\h*\\\\/\\\\/ if(!${$self}{alignDoubleBackSlash}); + + # possibly insert spaces infront of \\ + if(defined ${$self}{spacesBeforeDoubleBackSlash} and ${$self}{spacesBeforeDoubleBackSlash}>=0 and !${$self}{alignDoubleBackSlash}){ + my $horizontalSpaceToInsert = " "x (${$self}{spacesBeforeDoubleBackSlash}); + ${$_}{row} =~ s/\h*\\\\/$horizontalSpaceToInsert\\\\/; + } + } + + # if we have an empty row, it's possible that it originally had an end piece (e.g \\) and/or trailing comments + if(${$_}{row} =~ m/^\h*$/){ + ${$_}{row} .= (${$_}{endPiece} ? ${$_}{endPiece} :q() ).(${$_}{trailingComment}? ${$_}{trailingComment} : q() ); + } + } + + # to the log file + if($is_tt_switch_active){ + $self->logger(${$_}{row},'ttrace') for @formattedBody; + } + + # delete the original body + ${$self}{body} = q(); + + # update the body + ${$self}{body} .= ${$_}{row}."\n" for @formattedBody; + + # if the \end{} statement didn't originally have a line break before it, we need to remove the final + # line break added by the above + ${$self}{body} =~ s/\h*\R$//s if !${$self}{linebreaksAtEnd}{body}; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Arguments.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Arguments.pm new file mode 100644 index 00000000000..569cead9584 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Arguments.pm @@ -0,0 +1,380 @@ +package LatexIndent::Arguments; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/get_arguments_regexp find_opt_mand_arguments get_numbered_arg_regexp construct_arguments_regexp $optAndMandRegExp/; +our $ArgumentCounter; +our $optAndMandRegExp; +our $optAndMandRegExpWithLineBreaks; + +sub construct_arguments_regexp{ + my $self = shift; + + $optAndMandRegExp = $self->get_arguments_regexp; + + $optAndMandRegExpWithLineBreaks = $self->get_arguments_regexp(mode=>"lineBreaksAtEnd"); +} + +sub indent{ + my $self = shift; + $self->logger("Arguments object doesn't receive any direct indentation, but its children will...",'heading') if $is_t_switch_active; + return; +} + +sub find_opt_mand_arguments{ + my $self = shift; + + $self->logger("Searching ${$self}{name} for optional and mandatory arguments",'heading') if $is_t_switch_active; + + # blank line token + my $blankLineToken = $tokens{blanklines}; + + # the command object allows () + my $objectDependentOptAndMandRegExp = (defined ${$self}{optAndMandArgsRegExp} ? ${$self}{optAndMandArgsRegExp} : $optAndMandRegExpWithLineBreaks); + + if(${$self}{body} =~ m/^$objectDependentOptAndMandRegExp\h*($trailingCommentRegExp)?/){ + $self->logger("Optional/Mandatory arguments found in ${$self}{name}: $1",'heading') if $is_t_switch_active; + + # create a new Arguments object + # The arguments object is a little different to most + # other objects, as it is created purely for its children, + # so some of the properties common to other objects, such + # as environment, ifelsefi, etc do not exist for Arguments; + # they will, however, exist for its children: OptionalArgument, MandatoryArgument + my $arguments = LatexIndent::Arguments->new(begin=>"", + name=>${$self}{name}.":arguments", + parent=>${$self}{name}, + body=>$1, + linebreaksAtEnd=>{ + end=>$2?1:0, + }, + end=>"", + regexp=>$objectDependentOptAndMandRegExp, + endImmediatelyFollowedByComment=>$2?0:($3?1:0), + ); + + # give unique id + $arguments->create_unique_id; + + # determine which comes first, optional or mandatory + if(${$arguments}{body} =~ m/.*?((?<!\\)\{|\[)/s){ + + if($1 eq "\["){ + $self->logger("Searching for optional arguments, and then mandatory (optional found first)") if $is_t_switch_active; + # look for optional arguments + $arguments->find_optional_arguments; + + # look for mandatory arguments + $arguments->find_mandatory_arguments; + } else { + $self->logger("Searching for mandatory arguments, and then optional (mandatory found first)") if $is_t_switch_active; + # look for mandatory arguments + $arguments->find_mandatory_arguments; + + # look for optional arguments + $arguments->find_optional_arguments; + } + + } else { + $self->logger("Searching for round brackets ONLY") if $is_t_switch_active; + # look for round brackets + $arguments->find_round_brackets; + } + + # examine *first* child + # situation: parent BodyStartsOnOwnLine >= 1, but first child has BeginStartsOnOwnLine == 0 || BeginStartsOnOwnLine == undef + # problem: the *body* of parent actually starts after the arguments + # solution: remove the linebreak at the end of the begin statement of the parent + if(defined ${$self}{BodyStartsOnOwnLine} and ${$self}{BodyStartsOnOwnLine}>=1){ + if( !(defined ${${${$arguments}{children}}[0]}{BeginStartsOnOwnLine} and ${${${$arguments}{children}}[0]}{BeginStartsOnOwnLine}>=1) + and ${$self}{body} !~ m/^$blankLineToken/){ + my $BodyStringLogFile = ${$self}{aliases}{BodyStartsOnOwnLine}||"BodyStartsOnOwnLine"; + my $BeginStringLogFile = ${${${$arguments}{children}}[0]}{aliases}{BeginStartsOnOwnLine}||"BeginStartsOnOwnLine"; + $self->logger("$BodyStringLogFile = 1 (in ${$self}{name}), but first argument should not begin on its own line (see $BeginStringLogFile)") if $is_t_switch_active; + $self->logger("Removing line breaks at the end of ${$self}{begin}") if $is_t_switch_active; + ${$self}{begin} =~ s/\R*$//s; + ${$self}{linebreaksAtEnd}{begin} = 0; + } + } + + # situation: preserveBlankLines is active, so the body may well begin with a blank line token + # which means that ${$self}{linebreaksAtEnd}{begin} *should be* 1 + if(${${${$arguments}{children}}[0]}{body} =~ m/^($blankLineToken)/){ + $self->logger("Updating {linebreaksAtEnd}{begin} for ${$self}{name} as $blankLineToken or blank line found at beginning of argument child") if $is_t_switch_active; + ${$self}{linebreaksAtEnd}{begin} = 1 + } + + # examine *first* child + # situation: parent BodyStartsOnOwnLine == -1, but first child has BeginStartsOnOwnLine == 1 + # problem: the *body* of parent actually starts after the arguments + # solution: add a linebreak at the end of the begin statement of the parent so that + # the child settings are obeyed. + # BodyStartsOnOwnLine == 0 will actually be controlled by the last arguments' + # settings of EndFinishesWithLineBreak + if( ${$self}{linebreaksAtEnd}{begin} == 0 + and ((defined ${$self}{BodyStartsOnOwnLine} and ${$self}{BodyStartsOnOwnLine}==-1) + or !(defined ${$self}{BodyStartsOnOwnLine})) + ){ + if(defined ${${${$arguments}{children}}[0]}{BeginStartsOnOwnLine} and ${${${$arguments}{children}}[0]}{BeginStartsOnOwnLine}>=1){ + my $BodyStringLogFile = ${$self}{aliases}{BodyStartsOnOwnLine}||"BodyStartsOnOwnLine"; + my $BeginStringLogFile = ${${${$arguments}{children}}[0]}{aliases}{BeginStartsOnOwnLine}||"BeginStartsOnOwnLine"; + my $BodyValue = (defined ${$self}{BodyStartsOnOwnLine}) ? ${$self}{BodyStartsOnOwnLine} : "0"; + $self->logger("$BodyStringLogFile = $BodyValue (in ${$self}{name}), but first argument *should* begin on its own line (see $BeginStringLogFile)") if $is_t_switch_active; + + # possibly add a comment at the end of the begin statement + my $trailingCommentToken = q(); + if(${${${$arguments}{children}}[0]}{BeginStartsOnOwnLine}==1){ + $self->logger("Adding line breaks at the end of ${$self}{begin} (first argument, see $BeginStringLogFile == ${${${$arguments}{children}}[0]}{BeginStartsOnOwnLine})") if $is_t_switch_active; + } elsif(${${${$arguments}{children}}[0]}{BeginStartsOnOwnLine}==2){ + $self->logger("Adding a % at the end of begin, ${$self}{begin} followed by a linebreak ($BeginStringLogFile == 2)") if $is_t_switch_active; + $trailingCommentToken = "%".$self->add_comment_symbol; + $self->logger("Removing trailing space on ${$self}{begin}") if $is_t_switch_active; + ${$self}{begin} =~ s/\h*$//s; + } + + # modification + ${$self}{begin} .= "$trailingCommentToken\n"; + ${$self}{linebreaksAtEnd}{begin} = 1; + } + } + + # the replacement text can be just the ID, but the ID might have a line break at the end of it + ${$arguments}{replacementText} = ${$arguments}{id}; + + # children need to receive ancestor information, see test-cases/commands/commands-triple-nested.tex + foreach (@{${$arguments}{children}}){ + $self->logger("Updating argument children of ${$self}{name} to include ${$self}{id} in ancestors") if $is_t_switch_active; + push(@{${$_}{ancestors}},{ancestorID=>${$self}{id},ancestorIndentation=>${$self}{indentation},type=>"natural"}); + } + + # the argument object only needs a trailing line break if the *last* child + # did not add one at the end, and if BodyStartsOnOwnLine >= 1 + if( (defined ${${${$arguments}{children}}[-1]}{EndFinishesWithLineBreak} and ${${${$arguments}{children}}[-1]}{EndFinishesWithLineBreak}<1) + and (defined ${$self}{BodyStartsOnOwnLine} and ${$self}{BodyStartsOnOwnLine}>=1) ){ + $self->logger("Updating replacementtext to include a linebreak for arguments in ${$self}{name}") if $is_t_switch_active; + ${$arguments}{replacementText} .= "\n" if(${$arguments}{linebreaksAtEnd}{end}); + } + + # store children in special hash + push(@{${$self}{children}},$arguments); + + # remove the environment block, and replace with unique ID + ${$self}{body} =~ s/${$arguments}{regexp}/${$arguments}{replacementText}/; + + # delete the regexp, as there's no need for it + delete ${${${$self}{children}}[-1]}{regexp}; + + $self->logger(Dumper(\%{$arguments}),'ttrace') if($is_tt_switch_active); + $self->logger("replaced with ID: ${$arguments}{id}") if $is_t_switch_active; + } else { + $self->logger("... no arguments found") if $is_t_switch_active; + } + +} + + +sub create_unique_id{ + my $self = shift; + + $ArgumentCounter++; + ${$self}{id} = "$tokens{arguments}$ArgumentCounter$tokens{endOfToken}"; + return; +} + +sub get_numbered_arg_regexp{ + + # for example #1 #2, etc + my $numberedArgRegExp = qr/(?:#\d\h*;?,?\/?)+/; + return $numberedArgRegExp; +} + +sub get_arguments_regexp{ + + my $self = shift; + my %input = @_; + + # blank line token + my $blankLineToken = $tokens{blanklines}; + + # some calls to this routine need to account for the linebreaks at the end, some do not + my $lineBreaksAtEnd = (defined ${input}{mode} and ${input}{mode} eq 'lineBreaksAtEnd')?'\R*':q(); + + # for example #1 #2, etc + my $numberedArgRegExp = $self->get_numbered_arg_regexp; + + # beamer special + my $beamerRegExp = qr/\<.*?\>/; + + # commands are allowed strings between arguments, e.g node, decoration, etc, specified in stringsAllowedBetweenArguments + my $stringsBetweenArguments = q(); + + if(defined ${input}{stringBetweenArguments} and ${input}{stringBetweenArguments}==1){ + # grab the strings allowed between arguments + my %stringsAllowedBetweenArguments = %{${$masterSettings{commandCodeBlocks}}{stringsAllowedBetweenArguments}}; + + while( my ($allowedStringName,$allowedStringValue)= each %stringsAllowedBetweenArguments){ + # change + and - to escaped characters + $allowedStringName =~ s/\+/\\+/g; + $allowedStringName =~ s/\-/\\-/g; + + # form the regexp + $stringsBetweenArguments .= ($stringsBetweenArguments eq '' ? q() : "|").$allowedStringName if $allowedStringValue; + } + + # report to log file + $self->logger("Strings allowed between arguments $stringsBetweenArguments (see stringsAllowedBetweenArguments)",'heading') if $is_t_switch_active; + } + + if(defined ${input}{roundBrackets} and ${input}{roundBrackets}==1){ + # arguments regexp + return qr/ + ( # capture into $1 + (?: + (?:\h|\R|$blankLineToken|$trailingCommentRegExp|$numberedArgRegExp|$beamerRegExp|_|\^|$stringsBetweenArguments)* + (?: + (?: + \h* # 0 or more spaces + (?<!\\) # not immediately pre-ceeded by \ + \[ + (?: + (?! + (?:(?<!\\)\[|(?<!\\)\{) + ). + )*? # not including [, but \[ ok + (?<!\\) # not immediately pre-ceeded by \ + \] # [optional arguments] + ) + | # OR + (?: + \h* # 0 or more spaces + (?<!\\) # not immediately pre-ceeded by \ + \{ + (?: + (?! + (?:(?<!\\)\{|(?<!\\)\[) + ). + )*? # not including {, but \{ ok + (?<!\\) # not immediately pre-ceeded by \ + \} # {mandatory arguments} + ) + | # OR + (?: + \h* # 0 or more spaces + (?<!\\) # not immediately pre-ceeded by \ + \(\$ + (?: + (?! + (?:(?<!\\)\$) + ). + )*? # not including $ + \$ + (?<!\\) # not immediately pre-ceeded by \ + \) # {mandatory arguments} + ) + | # OR + (?: + \h* # 0 or more spaces + (?<!\\) # not immediately pre-ceeded by \ + \( + (?: + (?! + (?:(?<!\\)\() + ). + )*? # not including {, but \{ ok + (?<!\\) # not immediately pre-ceeded by \ + \) # {mandatory arguments} + ) + ) + ) + + # at least one of the above + # NOT followed by + (?! + (?: + (?:\h|\R|$blankLineToken|$trailingCommentRegExp|$numberedArgRegExp|$beamerRegExp|$stringsBetweenArguments)* # 0 or more h-space, blanklines, trailing comments + (?: + (?:(?<!\\)\[) + | + (?:(?<!\\)\{) + | + (?:(?<!\\)\() + ) + ) + ) + \h* + ($lineBreaksAtEnd) + ) + /sx; + } else { + return qr/ + ( # capture into $1 + (?: + (?:\h|\R|$blankLineToken|$trailingCommentRegExp|$numberedArgRegExp|$beamerRegExp|_|\^|$stringsBetweenArguments)* + (?: + (?: + \h* # 0 or more spaces + (?<!\\) # not immediately pre-ceeded by \ + \[ + (?: + (?! + (?:(?<!\\)\[|(?<!\\)\{) + ). + )*? # not including [, but \[ ok + (?<!\\) # not immediately pre-ceeded by \ + \] # [optional arguments] + ) + | # OR + (?: + \h* # 0 or more spaces + (?<!\\) # not immediately pre-ceeded by \ + \{ + (?: + (?! + (?:(?<!\\)\{|(?<!\\)\[) + ). + )*? # not including {, but \{ ok + (?<!\\) # not immediately pre-ceeded by \ + \} # {mandatory arguments} + ) + ) + ) + + # at least one of the above + # NOT followed by + (?! + (?: + (?:\h|\R|$blankLineToken|$trailingCommentRegExp|$numberedArgRegExp|$beamerRegExp)* # 0 or more h-space, blanklines, trailing comments + (?: + (?:(?<!\\)\[) + | + (?:(?<!\\)\{) + ) + ) + ) + \h* + ($lineBreaksAtEnd) + ) + /sx; + } +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/BackUpFileProcedure.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/BackUpFileProcedure.pm new file mode 100644 index 00000000000..dcb3b5bb74e --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/BackUpFileProcedure.pm @@ -0,0 +1,135 @@ +package LatexIndent::BackUpFileProcedure; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/%switches/; +use File::Basename; # to get the filename and directory path +use File::Copy; # to copy the original file to backup (if overwrite option set) +use Exporter qw/import/; +our @EXPORT_OK = qw/create_back_up_file/; + +# copy main file to a back up in the case of the overwrite switch being active + +sub create_back_up_file{ + my $self = shift; + + return unless($switches{overwrite}); + + # if we want to over write the current file create a backup first + $self->logger("Backup procedure (-w flag active):",'heading'); + + my $fileName = ${$self}{fileName}; + + # grab the file extension preferences + my %fileExtensionPreference= %{$masterSettings{fileExtensionPreference}}; + + # sort the file extensions by preference + my @fileExtensions = sort { $fileExtensionPreference{$a} <=> $fileExtensionPreference{$b} } keys(%fileExtensionPreference); + + # backup file name is the base name + my $backupFile = basename(${$self}{fileName},@fileExtensions); + + # add the user's backup directory to the backup path + $backupFile = "${$self}{cruftDirectory}/$backupFile"; + + # local variables, determined from the YAML settings + my $onlyOneBackUp = $masterSettings{onlyOneBackUp}; + my $maxNumberOfBackUps = $masterSettings{maxNumberOfBackUps}; + my $cycleThroughBackUps= $masterSettings{cycleThroughBackUps}; + my $backupExtension= $masterSettings{backupExtension}; + + # 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) { + $self->logger("WARNING: onlyOneBackUp=$onlyOneBackUp and maxNumberOfBackUps: $maxNumberOfBackUps"); + $self->logger("setting onlyOneBackUp=0 which will allow you to reach $maxNumberOfBackUps back ups"); + $onlyOneBackUp = 0; + } + + # if the user has specified that $maxNumberOfBackUps = 1 then + # they only want one backup + if($maxNumberOfBackUps==1) { + $onlyOneBackUp=1 ; + $self->logger("FYI: you set maxNumberOfBackUps=1, so I'm setting onlyOneBackUp: 1 "); + } elsif($maxNumberOfBackUps<=0 and !$onlyOneBackUp) { + $onlyOneBackUp=0 ; + $maxNumberOfBackUps=-1; + } + + # if onlyOneBackUp is set, then the backup file will + # be overwritten each time + if($onlyOneBackUp) { + $backupFile .= $backupExtension; + $self->logger("copying $fileName to $backupFile"); + $self->logger("$backupFile was overwritten (see onlyOneBackUp)") if (-e $backupFile); + } else { + # start with a backup file .bak0 (or whatever $backupExtension is present) + my $backupCounter = 0; + $backupFile .= $backupExtension.$backupCounter; + + # if it exists, then keep going: .bak0, .bak1, ... + while (-e $backupFile or $maxNumberOfBackUps>1) { + if($backupCounter==$maxNumberOfBackUps) { + $self->logger("maxNumberOfBackUps reached ($maxNumberOfBackUps, see maxNumberOfBackUps)"); + + # some users may wish to cycle through back up files, e.g: + # copy myfile.bak1 to myfile.bak0 + # copy myfile.bak2 to myfile.bak1 + # copy myfile.bak3 to myfile.bak2 + # + # current back up is stored in myfile.bak4 + if($cycleThroughBackUps) { + $self->logger("cycleThroughBackUps detected (see cycleThroughBackUps) "); + for(my $i=1;$i<=$maxNumberOfBackUps;$i++) { + # remove number from backUpFile + my $oldBackupFile = $backupFile; + $oldBackupFile =~ s/$backupExtension.*/$backupExtension/; + my $newBackupFile = $oldBackupFile; + + # add numbers back on + $oldBackupFile .= $i; + $newBackupFile .= $i-1; + + # check that the oldBackupFile exists + if(-e $oldBackupFile){ + $self->logger(" copying $oldBackupFile to $newBackupFile "); + copy($oldBackupFile,$newBackupFile) or die "Could not write to backup file $backupFile. Please check permissions. Exiting."; + } + } + } + + # rest maxNumberOfBackUps + $maxNumberOfBackUps=1 ; + last; # break out of the loop + } elsif(!(-e $backupFile)) { + $maxNumberOfBackUps=1; + last; # break out of the loop + } + $self->logger(" $backupFile already exists, incrementing by 1... (see maxNumberOfBackUps and onlyOneBackUp)"); + $backupCounter++; + $backupFile =~ s/$backupExtension.*/$backupExtension$backupCounter/; + } + $self->logger("copying $fileName to $backupFile"); + } + + # output these lines to the log file + $self->logger("Backup file: ",$backupFile,""); + $self->logger("Overwriting file: ",$fileName,""); + copy($fileName,$backupFile) or die "Could not write to backup file $backupFile. Please check permissions. Exiting."; +} +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/BlankLines.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/BlankLines.pm new file mode 100644 index 00000000000..f4572dd6797 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/BlankLines.pm @@ -0,0 +1,108 @@ +package LatexIndent::BlankLines; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/$is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +use Exporter qw/import/; +our @EXPORT_OK = qw/protect_blank_lines unprotect_blank_lines condense_blank_lines/; + +sub protect_blank_lines{ + return unless $is_m_switch_active; + my $self = shift; + + unless(${$masterSettings{modifyLineBreaks}}{preserveBlankLines}){ + $self->logger("Blank lines will not be protected (preserveBlankLines=0)",'heading') if $is_t_switch_active; + return + } + + $self->logger("Protecting blank lines (see preserveBlankLines)",'heading') if $is_t_switch_active; + ${$self}{body} =~ s/^(\h*)?\R/$tokens{blanklines}\n/mg; + return; +} + +sub condense_blank_lines{ + return unless $is_m_switch_active; + + return unless ${$masterSettings{modifyLineBreaks}}{condenseMultipleBlankLinesInto}>0; + + my $self = shift; + + # if preserveBlankLines is set to 0, then the blank-line-token will not be present + # in the document -- we change that here + if(${$masterSettings{modifyLineBreaks}}{preserveBlankLines}==0){ + # turn the switch on + ${$masterSettings{modifyLineBreaks}}{preserveBlankLines}=1; + + # log file information + $self->logger("Updating body to inclued blank line token, this requires preserveBlankLines = 1",'ttrace') if($is_tt_switch_active); + $self->logger("(any blanklines that could have been removed, would have done so by this point)",'ttrace') if($is_tt_switch_active); + + # make the call + $self->protect_blank_lines ; + $self->logger("body now looks like:\n${$self}{body}",'ttrace') if($is_tt_switch_active); + } + + # grab the value from the settings + my $condenseMultipleBlankLinesInto = ${$masterSettings{modifyLineBreaks}}{condenseMultipleBlankLinesInto}; + + # grab the blank-line-token + my $blankLineToken = $tokens{blanklines}; + + # condense! + $self->logger("Condensing multiple blank lines into $condenseMultipleBlankLinesInto (see condenseMultipleBlankLinesInto)",'heading') if $is_t_switch_active; + my $replacementToken = $blankLineToken; + for (my $i=1; $i<$condenseMultipleBlankLinesInto; $i++ ){ + $replacementToken .= "\n$blankLineToken"; + } + + $self->logger("blank line replacement token: $replacementToken",'ttrace') if($is_tt_switch_active); + ${$self}{body} =~ s/($blankLineToken\h*\R*\h*){1,}$blankLineToken/$replacementToken/mgs; + $self->logger("body now looks like:\n${$self}{body}",'ttrace') if($is_tt_switch_active); + return; +} + +sub unprotect_blank_lines{ + return unless $is_m_switch_active; + + return unless ${$masterSettings{modifyLineBreaks}}{preserveBlankLines}; + my $self = shift; + + $self->logger("Unprotecting blank lines (see preserveBlankLines)",'heading') if $is_t_switch_active; + my $blankLineToken = $tokens{blanklines}; + + # loop through the body, looking for the blank line token + while(${$self}{body} =~ m/$blankLineToken/m){ + # when the blank line token occupies the whole line + if(${$self}{body} =~ m/^\h*$blankLineToken$/m){ + $self->logger("Replacing purely blank lines",'heading') if $is_tt_switch_active; + ${$self}{body} =~ s/^\h*$blankLineToken$//mg; + $self->logger("body now looks like:\n${$self}{body}",'ttrace') if($is_tt_switch_active); + } + # otherwise the blank line has been deleted, so we compensate with an extra + if(${$self}{body} =~ m/(^\h*)?$blankLineToken/m){ + $self->logger("Replacing blank line token that doesn't take up whole line",'heading') if $is_tt_switch_active; + ${$self}{body} =~ s/(^\h*)?$blankLineToken/$1?"\n".$1:"\n"/me; + $self->logger("body now looks like:\n${$self}{body}",'ttrace') if($is_tt_switch_active); + } + } + $self->logger("Finished unprotecting lines (see preserveBlankLines)",'heading') if $is_t_switch_active; + $self->logger("body now looks like ${$self}{body}",'ttrace') if($is_tt_switch_active); +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Braces.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Braces.pm new file mode 100644 index 00000000000..c5122fd1275 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Braces.pm @@ -0,0 +1,188 @@ +package LatexIndent::Braces; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Command qw/$commandRegExp $commandRegExpTrailingComment $optAndMandAndRoundBracketsRegExpLineBreaks/; +use LatexIndent::KeyEqualsValuesBraces qw/$key_equals_values_bracesRegExp $key_equals_values_bracesRegExpTrailingComment/; +use LatexIndent::NamedGroupingBracesBrackets qw/$grouping_braces_regexp $grouping_braces_regexpTrailingComment/; +use LatexIndent::UnNamedGroupingBracesBrackets qw/$un_named_grouping_braces_RegExp $un_named_grouping_braces_RegExp_trailing_comment/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_commands_or_key_equals_values_braces/; +our $commandCounter; + +sub find_commands_or_key_equals_values_braces{ + + my $self = shift; + + $self->logger("Searching for commands with optional and/or mandatory arguments AND key = {value}",'heading') if $is_t_switch_active ; + + # match either a \\command or key={value} + while( ${$self}{body} =~ m/$commandRegExpTrailingComment/ + or + ${$self}{body} =~ m/$key_equals_values_bracesRegExpTrailingComment/ + or + ${$self}{body} =~ m/$grouping_braces_regexpTrailingComment/ + or + ${$self}{body} =~ m/$un_named_grouping_braces_RegExp_trailing_comment/ + ){ + if(${$self}{body} =~ m/$commandRegExpTrailingComment/){ + + # global substitution + ${$self}{body} =~ s/ + $commandRegExpTrailingComment + / + # log file output + $self->logger("command found: $2",'heading') if $is_t_switch_active ; + + # create a new command object + my $command = LatexIndent::Command->new(begin=>$1.$2.($3?$3:q()).($4?$4:q()), + name=>$2, + body=>$5.($8?$8:($10?$10:q())), # $8 is linebreak, $10 is trailing comment + end=>q(), + linebreaksAtEnd=>{ + begin=>$4?1:0, + end=>$8?1:0, # $8 is linebreak before comment check, $10 is after + }, + modifyLineBreaksYamlName=>"commands", + endImmediatelyFollowedByComment=>$8?0:($10?1:0), + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"CommandStartsOnOwnLine", + # body statements + BodyStartsOnOwnLine=>"CommandNameFinishesWithLineBreak", + }, + optAndMandArgsRegExp=>$optAndMandAndRoundBracketsRegExpLineBreaks, + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($command); + ${@{${$self}{children}}[-1]}{replacementText}.($8?($10?$10:q()):q()); + /xseg; + + } elsif (${$self}{body} =~ m/$key_equals_values_bracesRegExpTrailingComment/){ + + # global substitution + ${$self}{body} =~ s/ + $key_equals_values_bracesRegExpTrailingComment + / + # log file output + $self->logger("key_equals_values_braces found: $3",'heading') if $is_t_switch_active ; + + # create a new key_equals_values_braces object + my $key_equals_values_braces = LatexIndent::KeyEqualsValuesBraces->new( + begin=>($2?$2:q()).$3.$4.($5?$5:q()), + name=>$3, + body=>$6.($9?$9:($10?$10:q()).($11?$11:q())), # $9 is linebreak before comment check, $11 is trailing comment + end=>q(), + linebreaksAtEnd=>{ + begin=>$5?1:0, + end=>$9?1:0, # $9 is linebreak before comment check + }, + modifyLineBreaksYamlName=>"keyEqualsValuesBracesBrackets", + beginningbit=>$1, + endImmediatelyFollowedByComment=>$9?0:($11?1:0), + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"KeyStartsOnOwnLine", + # body statements + BodyStartsOnOwnLine=>"EqualsFinishesWithLineBreak", + }, + additionalAssignments=>["EqualsStartsOnOwnLine"], + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($key_equals_values_braces); + ${@{${$self}{children}}[-1]}{replacementText}.($9?($11?$11:q()):q()); + /xseg; + + } elsif (${$self}{body} =~ m/$grouping_braces_regexpTrailingComment/){ + + # global substitution + ${$self}{body} =~ s/ + $grouping_braces_regexpTrailingComment + / + # log file output + $self->logger("named grouping braces found: $2",'heading') if $is_t_switch_active ; + + # create a new key_equals_values_braces object + my $grouping_braces = LatexIndent::NamedGroupingBracesBrackets->new( + begin=>$2.($3?$3:q()).($4?$4:q()), + name=>$2, + body=>$5.($8?$8:($9?$9:q())), + end=>q(), + linebreaksAtEnd=>{ + begin=>$4?1:0, + end=>$8?1:0, + }, + modifyLineBreaksYamlName=>"namedGroupingBracesBrackets", + beginningbit=>$1, + endImmediatelyFollowedByComment=>$8?0:($9?1:0), + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"NameStartsOnOwnLine", + # body statements + BodyStartsOnOwnLine=>"NameFinishesWithLineBreak", + }, + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($grouping_braces); + ${@{${$self}{children}}[-1]}{replacementText}.($8?($9?$9:q()):q()); + /xseg; + + } elsif (${$self}{body} =~ m/$un_named_grouping_braces_RegExp_trailing_comment/) { + # global substitution + ${$self}{body} =~ s/ + $un_named_grouping_braces_RegExp_trailing_comment + / + # log file output + $self->logger("UNnamed grouping braces found: (no name, by definition!)",'heading') if $is_t_switch_active ; + + # create a new Un-named-grouping-braces-brackets object + my $un_named_grouping_braces = LatexIndent::UnNamedGroupingBracesBrackets->new( + begin=>q(), + name=>"always-un-named", + body=>$3.($6?$6:($8?$8:q())), + end=>q(), + linebreaksAtEnd=>{ + begin=>$2?1:0, + end=>$6?1:0, + }, + modifyLineBreaksYamlName=>"UnNamedGroupingBracesBrackets", + beginningbit=>$1.($2?$2:q()), + endImmediatelyFollowedByComment=>$6?0:($8?1:0), + # begin statements + BeginStartsOnOwnLine=>0, + # body statements + BodyStartsOnOwnLine=>0, + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($un_named_grouping_braces); + ${@{${$self}{children}}[-1]}{replacementText}.($6?($8?$8:q()):q()); + /xseg; + + } + } + return; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Command.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Command.pm new file mode 100755 index 00000000000..022064fd1d7 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Command.pm @@ -0,0 +1,178 @@ +package LatexIndent::Command; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/construct_command_regexp $commandRegExp $commandRegExpTrailingComment $optAndMandAndRoundBracketsRegExpLineBreaks/; +our $commandCounter; +our $commandRegExp; +our $commandRegExpTrailingComment; +our $optAndMandAndRoundBracketsRegExp; +our $optAndMandAndRoundBracketsRegExpLineBreaks; + +# store the regular expresssion for matching and replacing +sub construct_command_regexp{ + my $self = shift; + + $optAndMandAndRoundBracketsRegExp = $self->get_arguments_regexp( + roundBrackets=>${$masterSettings{commandCodeBlocks}}{roundParenthesesAllowed}, + stringBetweenArguments=>1); + + $optAndMandAndRoundBracketsRegExpLineBreaks = $self->get_arguments_regexp( + roundBrackets=>${$masterSettings{commandCodeBlocks}}{roundParenthesesAllowed}, + mode=>"lineBreaksAtEnd", + stringBetweenArguments=>1); + + # construct the command regexp + $commandRegExp = qr/ + (\\|@) + ( + [+a-zA-Z@\*0-9_\:]+? # lowercase|uppercase letters, @, *, numbers + ) + (\h*) + (\R*)? + ($optAndMandAndRoundBracketsRegExp) + (\R)? + /sx; + + # command regexp with trailing comment + $commandRegExpTrailingComment = qr/$commandRegExp(\h*)((?:$trailingCommentRegExp\h*)*)/; + +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # check for adding/removing linebreaks before = + $self->check_linebreaks_before_equals; + + # search for arguments + $self->find_opt_mand_arguments; + + # situation: ${${$self}{linebreaksAtEnd}}{end} == 1, and the argument container object + # still contains a linebreak at the end; in this case, we need to remove the linebreak from + # the container object + if(${${$self}{linebreaksAtEnd}}{end} == 1 + and ${${${$self}{children}}[0]}{body} =~ m/\R$/s + and !${$self}{endImmediatelyFollowedByComment}){ + $self->logger("Removing linebreak from argument container of ${$self}{name}") if $is_t_switch_active; + ${${${$self}{children}}[0]}{body} =~ s/\R$//s; + ${${${${$self}{children}}[0]}{linebreaksAtEnd}}{body} = 0; + } + + # situation: ${${$self}{linebreaksAtEnd}}{end} == 1 and the last argument specifies + # EndFinishesWithLineBreaks = 0 (see test-cases/commands/just-one-command-mod10.tex) + if(${${$self}{linebreaksAtEnd}}{end} == 1 + and defined ${${${${$self}{children}}[0]}{children}[-1]}{EndFinishesWithLineBreak} + and ${${${${$self}{children}}[0]}{children}[-1]}{EndFinishesWithLineBreak} == -1 + ){ + $self->logger("Switching linebreaksAtEnd{end} to be 0 in command ${$self}{name} as last argument specifies EndFinishesWithLineBreak == 0") if $is_t_switch_active; + ${${$self}{linebreaksAtEnd}}{end} = 0; + ${$self}{EndFinishesWithLineBreak} = -1; + } + + # if the last argument finishes with a linebreak, it won't get interpreted at + # the right time (see test-cases/commands/commands-one-line-nested-simple-mod1.tex for example) + # so this little bit fixes it + if(${${${${${$self}{children}}[0]}{children}[-1]}{linebreaksAtEnd}}{end} and ${${$self}{linebreaksAtEnd}}{end} == 0 + and defined ${${${${$self}{children}}[0]}{children}[-1]}{EndFinishesWithLineBreak} + and ${${${${$self}{children}}[0]}{children}[-1]}{EndFinishesWithLineBreak} >= 1 + and !${$self}{endImmediatelyFollowedByComment}){ + + # update the Command object + $self->logger("Adjusting linebreaksAtEnd in command ${$self}{name}") if $is_t_switch_active; + ${${$self}{linebreaksAtEnd}}{end} = ${${${${${$self}{children}}[0]}{children}[-1]}{linebreaksAtEnd}}{end}; + ${$self}{replacementText} .= "\n"; + + # update the argument object + $self->logger("Adjusting argument object in command, ${$self}{name}") if $is_t_switch_active; + ${${${${$self}{children}}[0]}{linebreaksAtEnd}}{body} = 0; + ${${${$self}{children}}[0]}{body} =~ s/\R$//s; + + # update the last mandatory/optional argument + $self->logger("Adjusting last argument in command, ${$self}{name}") if $is_t_switch_active; + ${${${${${$self}{children}}[0]}{children}[-1]}{linebreaksAtEnd}}{end} = 0; + ${${${${$self}{children}}[0]}{children}[-1]}{EndFinishesWithLineBreak} = -1; + ${${${${$self}{children}}[0]}{children}[-1]}{replacementText} =~ s/\R$//s; + + # output to log file + $self->logger(Dumper(${${${$self}{children}}[0]}{children}[-1])) if $is_t_switch_active; + } + + # situation: ${${$self}{linebreaksAtEnd}}{end} == 1 and the last argument has added + # a line break, which can result in a bogus blank line (see test-cases/commands/just-one-command.tex with mand-args-mod1.yaml) + if(${${$self}{linebreaksAtEnd}}{end} == 1 + and defined ${${${${$self}{children}}[0]}{children}[-1]}{EndFinishesWithLineBreak} + and ${${${${$self}{children}}[0]}{children}[-1]}{EndFinishesWithLineBreak} >= 1 + and ${${${${$self}{children}}[0]}{children}[-1]}{replacementText}=~m/\R$/s + and !${$self}{endImmediatelyFollowedByComment}){ + + # last argument adjustment + $self->logger("Adjusting last argument in command, ${$self}{name} to avoid double line break") if $is_t_switch_active; + ${${${${$self}{children}}[0]}{children}[-1]}{replacementText}=~s/\R$//s; + ${${${${${$self}{children}}[0]}{children}[-1]}{linebreaksAtEnd}}{end} = 0; + + # argument object adjustment + $self->logger("Adjusting argument object in command, ${$self}{name} to avoid double line break") if $is_t_switch_active; + ${${${${$self}{children}}[0]}{linebreaksAtEnd}}{body} = 0; + ${${${$self}{children}}[0]}{body}=~s/\R$//s; + } + + # the arguments body might finish with horizontal space, in which case, we need to transfer this + # to the parent object replacement text. + # + # see ../test-cases/texexchange/5461.tex which was the first example to demonstrate the need for this + if(!${${${$self}{children}}[0]}{endImmediatelyFollowedByComment} and ${${${$self}{children}}[0]}{body} =~ m/\h*$/ and ${$self}{replacementText} !~ m/\R$/){ + $self->logger("${$self}{name}: trailling horizontal space found in arguments -- removing it from arguments, adding to replacement text") if $is_t_switch_active; + ${${${$self}{children}}[0]}{body} =~ s/(\h*)$//s; + ${$self}{replacementText} .= "$1"; + } + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for special begin/end + $self->find_special; + +} + +sub check_linebreaks_before_equals{ + # empty routine, which allows the above routine to function (this routine kicks in for KeyEqualsValuesBraces) + return; +} + +sub create_unique_id{ + my $self = shift; + + $commandCounter++; + ${$self}{id} = "$tokens{command}$commandCounter"; + return; +} + +sub align_at_ampersand{ + # need an empty routine here for commands; see + # test-cases/matrix1.tex for example + return; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Document.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Document.pm new file mode 100644 index 00000000000..7c951ec7995 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Document.pm @@ -0,0 +1,335 @@ +package LatexIndent::Document; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use Data::Dumper; + +# gain access to subroutines in the following modules +use LatexIndent::Switches qw/storeSwitches %switches $is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +use LatexIndent::LogFile qw/logger output_logfile processSwitches/; +use LatexIndent::GetYamlSettings qw/readSettings modify_line_breaks_settings get_indentation_settings_for_this_object get_every_or_custom_value get_indentation_information get_object_attribute_for_indentation_settings alignment_at_ampersand_settings/; +use LatexIndent::FileExtension qw/file_extension_check/; +use LatexIndent::BackUpFileProcedure qw/create_back_up_file/; +use LatexIndent::BlankLines qw/protect_blank_lines unprotect_blank_lines condense_blank_lines/; +use LatexIndent::ModifyLineBreaks qw/modify_line_breaks_body modify_line_breaks_end remove_line_breaks_begin adjust_line_breaks_end_parent/; +use LatexIndent::TrailingComments qw/remove_trailing_comments put_trailing_comments_back_in add_comment_symbol construct_trailing_comment_regexp/; +use LatexIndent::HorizontalWhiteSpace qw/remove_trailing_whitespace remove_leading_space/; +use LatexIndent::Indent qw/indent wrap_up_statement determine_total_indentation indent_begin indent_body indent_end_statement final_indentation_check get_surrounding_indentation indent_children_recursively check_for_blank_lines_at_beginning put_blank_lines_back_in_at_beginning add_surrounding_indentation_to_begin_statement/; +use LatexIndent::Tokens qw/token_check %tokens/; +use LatexIndent::HiddenChildren qw/find_surrounding_indentation_for_children update_family_tree get_family_tree check_for_hidden_children/; +use LatexIndent::AlignmentAtAmpersand qw/align_at_ampersand find_aligned_block/; +use LatexIndent::DoubleBackSlash qw/dodge_double_backslash un_dodge_double_backslash/; + +# code blocks +use LatexIndent::Verbatim qw/put_verbatim_back_in find_verbatim_environments find_noindent_block find_verbatim_commands put_verbatim_commands_back_in/; +use LatexIndent::Environment qw/find_environments/; +use LatexIndent::IfElseFi qw/find_ifelsefi/; +use LatexIndent::Arguments qw/get_arguments_regexp find_opt_mand_arguments get_numbered_arg_regexp construct_arguments_regexp/; +use LatexIndent::OptionalArgument qw/find_optional_arguments/; +use LatexIndent::MandatoryArgument qw/find_mandatory_arguments get_mand_arg_reg_exp/; +use LatexIndent::RoundBrackets qw/find_round_brackets/; +use LatexIndent::Item qw/find_items construct_list_of_items/; +use LatexIndent::Braces qw/find_commands_or_key_equals_values_braces/; +use LatexIndent::Command qw/construct_command_regexp/; +use LatexIndent::KeyEqualsValuesBraces qw/construct_key_equals_values_regexp/; +use LatexIndent::NamedGroupingBracesBrackets qw/construct_grouping_braces_brackets_regexp/; +use LatexIndent::UnNamedGroupingBracesBrackets qw/construct_unnamed_grouping_braces_brackets_regexp/; +use LatexIndent::Special qw/find_special construct_special_begin/; +use LatexIndent::Heading qw/find_heading construct_headings_levels/; +use LatexIndent::FileContents qw/find_file_contents_environments_and_preamble/; +use LatexIndent::Preamble; + +sub new{ + # Create new objects, with optional key/value pairs + # passed as initializers. + # + # See Programming Perl, pg 319 + my $invocant = shift; + my $class = ref($invocant) || $invocant; + my $self = {@_}; + bless ($self,$class); + return $self; +} + +sub latexindent{ + my $self = shift; + $self->storeSwitches; + $self->processSwitches; + $self->readSettings; + $self->file_extension_check; + $self->operate_on_file; +} + +sub operate_on_file{ + my $self = shift; + + $self->create_back_up_file; + $self->token_check; + $self->construct_regular_expressions; + $self->find_noindent_block; + $self->find_verbatim_commands; + $self->find_aligned_block; + $self->remove_trailing_comments; + $self->find_verbatim_environments; + $self->protect_blank_lines; + $self->remove_trailing_whitespace(when=>"before"); + $self->find_file_contents_environments_and_preamble; + $self->dodge_double_backslash; + $self->remove_leading_space; + $self->process_body_of_text; + $self->remove_trailing_whitespace(when=>"after"); + $self->condense_blank_lines; + $self->unprotect_blank_lines; + $self->un_dodge_double_backslash; + $self->put_verbatim_back_in; + $self->put_trailing_comments_back_in; + $self->put_verbatim_commands_back_in; + $self->output_indented_text; + $self->output_logfile; + return +} + +sub construct_regular_expressions{ + my $self = shift; + $self->construct_trailing_comment_regexp; + $self->construct_list_of_items; + $self->construct_special_begin; + $self->construct_headings_levels; + $self->construct_arguments_regexp; + $self->construct_command_regexp; + $self->construct_key_equals_values_regexp; + $self->construct_grouping_braces_brackets_regexp; + $self->construct_unnamed_grouping_braces_brackets_regexp; + +} + +sub output_indented_text{ + my $self = shift; + + # output to screen, unless silent mode + print ${$self}{body} unless $switches{silentMode}; + + $self->logger("Output routine",'heading'); + + # if -overwrite is active then output to original fileName + if($switches{overwrite}) { + $self->logger("Overwriting file ${$self}{fileName}"); + open(OUTPUTFILE,">",${$self}{fileName}); + print OUTPUTFILE ${$self}{body}; + close(OUTPUTFILE); + } elsif($switches{outputToFile}) { + $self->logger("Outputting to file $switches{outputToFile}"); + open(OUTPUTFILE,">",$switches{outputToFile}); + print OUTPUTFILE ${$self}{body}; + close(OUTPUTFILE); + } else { + $self->logger("Not outputting to file; see -w and -o switches for more options."); + } + return; +} + +sub process_body_of_text{ + my $self = shift; + + # find objects recursively + $self->logger('Phase 1: searching for objects','heading'); + $self->find_objects; + + # find all hidden child + $self->logger('Phase 2: finding surrounding indentation','heading'); + $self->find_surrounding_indentation_for_children; + + # indentation recursively + $self->logger('Phase 3: indenting objects','heading'); + $self->indent_children_recursively; + + # final indentation check + $self->logger('Phase 4: final indentation check','heading'); + $self->final_indentation_check; + + return; +} + +sub find_objects{ + my $self = shift; + + # search for environments + $self->logger('looking for ENVIRONMENTS'); + $self->find_environments; + + # search for ifElseFi blocks + $self->logger('looking for IFELSEFI'); + $self->find_ifelsefi; + + # search for headings (part, chapter, section, setc) + $self->logger('looking for HEADINGS (chapter, section, part, etc)'); + $self->find_heading; + + # search for commands with arguments + $self->logger('looking for COMMANDS and key = {value}'); + $self->find_commands_or_key_equals_values_braces; + + # search for special begin/end + $self->logger('looking for SPECIAL begin/end'); + $self->find_special; + + # if there are no children, return + if(${$self}{children}){ + $self->logger("Objects have been found.",'heading'); + } else { + $self->logger("No objects found."); + return; + } + + # logfile information + $self->logger(Dumper(\%{$self}),'ttrace') if($is_tt_switch_active); + $self->logger("Operating on: ${$self}{name}",'heading'); + $self->logger("Number of children:",'heading'); + $self->logger(scalar (@{${$self}{children}})); + + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + $self->logger("There are no tasks particular to ${$self}{name}") if $is_t_switch_active; +} + +sub get_settings_and_store_new_object{ + my $self = shift; + + # grab the object to be operated upon + my ($latexIndentObject) = @_; + + # there are a number of tasks common to each object + $latexIndentObject->tasks_common_to_each_object(%{$self}); + + # tasks particular to each object + $latexIndentObject->tasks_particular_to_each_object; + + # store children in special hash + push(@{${$self}{children}},$latexIndentObject); + +} + +sub tasks_common_to_each_object{ + my $self = shift; + + # grab the parent information + my %parent = @_; + + # update/create the ancestor information + if($parent{ancestors}){ + $self->logger("Ancestors *have* been found for ${$self}{name}") if($is_t_switch_active); + push(@{${$self}{ancestors}},@{$parent{ancestors}}); + } else { + $self->logger("No ancestors found for ${$self}{name}") if($is_t_switch_active); + if(defined $parent{id} and $parent{id} ne ''){ + $self->logger("Creating ancestors with $parent{id} as the first one") if($is_t_switch_active); + push(@{${$self}{ancestors}},{ancestorID=>$parent{id},ancestorIndentation=>\$parent{indentation},type=>"natural",name=>${$self}{name}}); + } + } + + # natural ancestors + ${$self}{naturalAncestors} = q(); + if(${$self}{ancestors}){ + ${$self}{naturalAncestors} .= "---".${$_}{ancestorID}."\n" for @{${$self}{ancestors}}; + } + + # in what follows, $self can be an environment, ifElseFi, etc + + # count linebreaks in body + my $bodyLineBreaks = 0; + $bodyLineBreaks++ while(${$self}{body} =~ m/\R/sxg); + ${$self}{bodyLineBreaks} = $bodyLineBreaks; + + # get settings for this object + $self->get_indentation_settings_for_this_object; + + # give unique id + $self->create_unique_id; + + # add trailing text to the id to stop, e.g LATEX-INDENT-ENVIRONMENT1 matching LATEX-INDENT-ENVIRONMENT10 + ${$self}{id} .= $tokens{endOfToken}; + + # the replacement text can be just the ID, but the ID might have a line break at the end of it + $self->get_replacement_text; + + # the above regexp, when used below, will remove the trailing linebreak in ${$self}{linebreaksAtEnd}{end} + # so we compensate for it here + $self->adjust_replacement_text_line_breaks_at_end; + + # modify line breaks on body and end statements + $self->modify_line_breaks_body; + + # modify line breaks end statements + $self->modify_line_breaks_end; + + # check the body for current children + $self->check_for_hidden_children; + + return; +} + +sub get_replacement_text{ + my $self = shift; + + # the replacement text can be just the ID, but the ID might have a line break at the end of it + ${$self}{replacementText} = ${$self}{id}; + return; +} + +sub adjust_replacement_text_line_breaks_at_end{ + my $self = shift; + + # the above regexp, when used below, will remove the trailing linebreak in ${$self}{linebreaksAtEnd}{end} + # so we compensate for it here + $self->logger("Putting linebreak after replacementText for ${$self}{name}") if($is_t_switch_active); + if(defined ${$self}{horizontalTrailingSpace}){ + ${$self}{replacementText} .= ${$self}{horizontalTrailingSpace} unless(!${$self}{endImmediatelyFollowedByComment} and defined ${$self}{EndFinishesWithLineBreak} and ${$self}{EndFinishesWithLineBreak}==2); + } + ${$self}{replacementText} .= "\n" if(${$self}{linebreaksAtEnd}{end}); + +} + +sub count_body_line_breaks{ + my $self = shift; + + my $oldBodyLineBreaks = (defined ${$self}{bodyLineBreaks})? ${$self}{bodyLineBreaks} : 0; + + # count linebreaks in body + my $bodyLineBreaks = 0; + $bodyLineBreaks++ while(${$self}{body} =~ m/\R/sxg); + ${$self}{bodyLineBreaks} = $bodyLineBreaks; + $self->logger("bodyLineBreaks ${$self}{bodyLineBreaks}") if((${$self}{bodyLineBreaks} != $oldBodyLineBreaks) and $is_t_switch_active); +} + +sub wrap_up_tasks{ + my $self = shift; + + # most recent child object + my $child = @{${$self}{children}}[-1]; + + # check if the last object was the last thing in the body, and if it has adjusted linebreaks + $self->adjust_line_breaks_end_parent; + + $self->logger(Dumper(\%{$child})) if($is_tt_switch_active); + $self->logger("replaced with ID: ${$child}{id}") if $is_t_switch_active; + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/DoubleBackSlash.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/DoubleBackSlash.pm new file mode 100644 index 00000000000..8b8be3e25ec --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/DoubleBackSlash.pm @@ -0,0 +1,45 @@ +package LatexIndent::DoubleBackSlash; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use LatexIndent::Tokens qw/%tokens/; +use Exporter qw/import/; +our @EXPORT_OK = qw/dodge_double_backslash un_dodge_double_backslash/; + +# some code can contain, e.g +# cycle list={blue,mark=none\\}, +# see test-cases/texexchange/29293-christian-feuersanger.tex +# +# This is problematic, as the argument regexp won't count the right } because it has a +# backslash immediately infront of it! +sub dodge_double_backslash{ + my $self = shift; + + ${$self}{body} =~ s/(?:\\\\(\{|\}|\]))/$tokens{doubleBackSlash}$1/sg; + return; +} + +# this routine replaces the token with the \\\\ +sub un_dodge_double_backslash{ + my $self = shift; + + ${$self}{body} =~ s/$tokens{doubleBackSlash}/\\\\/sg; + return; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Environment.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Environment.pm new file mode 100755 index 00000000000..1b0ad27d550 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Environment.pm @@ -0,0 +1,128 @@ +package LatexIndent::Environment; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_environments/; +our $environmentCounter; + +# store the regular expresssion for matching and replacing the \begin{}...\end{} statements +our $environmentRegExp = qr/ + ( + \\begin\{ + ( + [a-zA-Z@\*0-9_\\]+ # lowercase|uppercase letters, @, *, numbers + ) # environment name captured into $2 + \} # \begin{<something>} statement + \h* # horizontal space + (\R*)? # possible line breaks (into $3) + ) # begin statement captured into $1 + ( + (?: # cluster-only (), don't capture + (?! # don't include \begin in the body + (?:\\begin) # cluster-only (), don't capture + ). # any character, but not \\begin + )*? # non-greedy + (\R*)? # possible line breaks (into $5) + ) # environment body captured into $4 + ( + \\end\{\2\} # \end{<something>} statement + ) # captured into $6 + (\h*)? # possibly followed by horizontal space + (\R)? # possibly followed by a line break + /sx; + +sub find_environments{ + my $self = shift; + + + while( ${$self}{body} =~ m/$environmentRegExp\h*($trailingCommentRegExp)?/){ + + # global substitution + ${$self}{body} =~ s/ + $environmentRegExp(\h*)($trailingCommentRegExp)? + / + # log file output + $self->logger("environment found: $2",'heading') if $is_t_switch_active; + + # create a new Environment object + my $env = LatexIndent::Environment->new(begin=>$1, + name=>$2, + body=>$4, + end=>$6, + linebreaksAtEnd=>{ + begin=>$3?1:0, + body=>$5?1:0, + end=>$8?1:0, + }, + modifyLineBreaksYamlName=>"environments", + endImmediatelyFollowedByComment=>$8?0:($10?1:0), + horizontalTrailingSpace=>$7?$7:q(), + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($env); + ${@{${$self}{children}}[-1]}{replacementText}.($9?$9:q()).($10?$10:q()); + /xseg; + $self->adjust_line_breaks_end_parent; + } + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # if the environment is empty, we may need to update linebreaksAtEnd{body} + if(${$self}{body} =~ m/^\h*$/s and ${${$self}{linebreaksAtEnd}}{begin}){ + $self->logger("empty environment body (${$self}{name}), updating linebreaksAtEnd{body} to be 1") if($is_t_switch_active); + ${${$self}{linebreaksAtEnd}}{body} = 1; + } + + # search for items as the first order of business + $self->find_items; + + # search for headings (important to do this before looking for commands!) + $self->find_heading; + + # search for commands, keys, named grouping braces + $self->find_commands_or_key_equals_values_braces; + + # search for arguments + $self->find_opt_mand_arguments; + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for special begin/end + $self->find_special; +} + +sub create_unique_id{ + my $self = shift; + + $environmentCounter++; + ${$self}{id} = "$tokens{environment}$environmentCounter"; + return; +} + + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/FileContents.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/FileContents.pm new file mode 100644 index 00000000000..dc8aa53f206 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/FileContents.pm @@ -0,0 +1,230 @@ +package LatexIndent::FileContents; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @EXPORT_OK = qw/find_file_contents_environments_and_preamble/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our $fileContentsCounter; + +sub find_file_contents_environments_and_preamble{ + my $self = shift; + + # store the file contents blocks in an array which, depending on the value + # of indentPreamble, will be put into the verbatim hash, or otherwise + # stored as children to be operated upon + my @fileContentsStorageArray; + + # fileContents environments + $self->logger('looking for FILE CONTENTS environments (see fileContentsEnvironments)','heading'); + $self->logger(Dumper(\%{$masterSettings{fileContentsEnvironments}})) if($is_t_switch_active); + while( my ($fileContentsEnv,$yesno)= each %{$masterSettings{fileContentsEnvironments}}){ + if($yesno){ + $self->logger("looking for $fileContentsEnv:$yesno environments"); + + # the trailing * needs some care + if($fileContentsEnv =~ m/\*$/){ + $fileContentsEnv =~ s/\*$//; + $fileContentsEnv .= '\*'; + } + + my $fileContentsRegExp = qr/ + ( + \\begin\{ + $fileContentsEnv + \} + ) + ( + .*? + ) + ( + \\end\{$fileContentsEnv\} + \h* + ) + (\R)? + /sx; + + while( ${$self}{body} =~ m/$fileContentsRegExp/sx){ + + # create a new Environment object + my $fileContentsBlock = LatexIndent::FileContents->new( begin=>$1, + body=>$2, + end=>$3, + name=>$fileContentsEnv, + linebreaksAtEnd=>{ + begin=>0, + body=>0, + end=>$4?1:0, + }, + modifyLineBreaksYamlName=>"filecontents", + ); + # give unique id + $fileContentsBlock->create_unique_id; + + # the replacement text can be just the ID, but the ID might have a line break at the end of it + $fileContentsBlock->get_replacement_text; + + # count body line breaks + $fileContentsBlock->count_body_line_breaks; + + # the above regexp, when used below, will remove the trailing linebreak in ${$self}{linebreaksAtEnd}{end} + # so we compensate for it here + $fileContentsBlock->adjust_replacement_text_line_breaks_at_end; + + # store the fileContentsBlock, and determine location afterwards + push(@fileContentsStorageArray,$fileContentsBlock); + + # log file output + $self->logger("FILECONTENTS environment found: $fileContentsEnv"); + + # remove the environment block, and replace with unique ID + ${$self}{body} =~ s/$fileContentsRegExp/${$fileContentsBlock}{replacementText}/sx; + + $self->logger("replaced with ID: ${$fileContentsBlock}{id}"); + } + } else { + $self->logger("*not* looking for $fileContentsEnv as $fileContentsEnv:$yesno"); + } + } + + # determine if body of document contains \begin{document} -- if it does, then assume + # that the body has a preamble + my $preambleRegExp = qr/ + (.*?) + (\R*)? # linebreaks at end of body into $2 + \\begin\{document\} + /sx; + my $preamble = q(); + + my $needToStorePreamble = 0; + + # try and find the preamble + if( ${$self}{body} =~ m/$preambleRegExp/sx and ${$masterSettings{lookForPreamble}}{${$self}{fileExtension}}){ + + $self->logger("\\begin{document} found in body (after searching for filecontents)-- assuming that a preamble exists"); + + # create a preamble object + $preamble = LatexIndent::Preamble->new( begin=>q(), + body=>$1, + end=>q(), + name=>"preamble", + linebreaksAtEnd=>{ + begin=>0, + body=>$2?1:0, + end=>0, + }, + afterbit=>($2?$2:q())."\\begin{document}", + modifyLineBreaksYamlName=>"preamble", + ); + + # give unique id + $preamble->create_unique_id; + + # get the replacement_text + $preamble->get_replacement_text; + + # log file output + $self->logger("preamble found: $preamble"); + + # remove the environment block, and replace with unique ID + ${$self}{body} =~ s/$preambleRegExp/${$preamble}{replacementText}/sx; + + $self->logger("replaced with ID: ${$preamble}{replacementText}"); + # indentPreamble set to 1 + if($masterSettings{indentPreamble}){ + $self->logger("storing ${$preamble}{id} for indentation (see indentPreamble)"); + $needToStorePreamble = 1; + } else { + # indentPreamble set to 0 + $self->logger("NOT storing ${$preamble}{id} for indentation -- will store as VERBATIM object (see indentPreamble)"); + $preamble->unprotect_blank_lines; + ${$self}{verbatim}{${$preamble}{id}} = $preamble; + } + } + + # loop through the fileContents array, check if it's in the preamble + foreach(@fileContentsStorageArray){ + my $indentThisChild = 0; + # verbatim children go in special hash + if($preamble ne '' and ${$preamble}{body} =~ m/${$_}{id}/){ + $self->logger("filecontents (${$_}{id}) is within preamble"); + # indentPreamble set to 1 + if($masterSettings{indentPreamble}){ + $self->logger("storing ${$_}{id} for indentation (indentPreamble is 1)"); + $indentThisChild = 1; + } else { + # indentPreamble set to 0 + $self->logger("Storing ${$_}{id} as a VERBATIM object (indentPreamble is 0)"); + ${$self}{verbatim}{${$_}{id}}=$_; + } + } else { + $self->logger("storing ${$_}{id} for indentation (${$_}{name} found outside of preamble)"); + $indentThisChild = 1; + } + # store the child, if necessary + if($indentThisChild){ + $_->remove_leading_space; + $_->get_indentation_settings_for_this_object; + $_->tasks_particular_to_each_object; + push(@{${$self}{children}},$_); + } + } + + if($needToStorePreamble){ + $preamble->dodge_double_backslash; + $preamble->remove_leading_space; + $preamble->find_commands_or_key_equals_values_braces if($masterSettings{preambleCommandsBeforeEnvironments}); + $preamble->tasks_particular_to_each_object; + push(@{${$self}{children}},$preamble); + } + return; +} + +sub create_unique_id{ + my $self = shift; + + $fileContentsCounter++; + ${$self}{id} = "$tokens{filecontents}$fileContentsCounter$tokens{endOfToken}"; + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # search for environments + $self->find_environments; + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for headings (part, chapter, section, setc) + $self->find_heading; + + # search for commands with arguments + $self->find_commands_or_key_equals_values_braces; + + # search for special begin/end + $self->find_special; + + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/FileExtension.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/FileExtension.pm new file mode 100644 index 00000000000..8dcb66dd80c --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/FileExtension.pm @@ -0,0 +1,99 @@ +package LatexIndent::FileExtension; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use File::Basename; # to get the filename and directory path +use Exporter qw/import/; +our @EXPORT_OK = qw/file_extension_check/; + +sub file_extension_check{ + my $self = shift; + + # grab the filename + my $fileName = ${$self}{fileName}; + + # grab the file extension preferences + my %fileExtensionPreference= %{$masterSettings{fileExtensionPreference}}; + + # sort the file extensions by preference + my @fileExtensions = sort { $fileExtensionPreference{$a} <=> $fileExtensionPreference{$b} } keys(%fileExtensionPreference); + + # get the base file name, allowing for different extensions (possibly no extension) + my ($dir, $name, $ext) = fileparse($fileName, @fileExtensions); + + # check to make sure given file type is supported + if( -e $fileName and !$ext ){ + my $message = "The file $fileName exists , but the extension does not correspond to any given in fileExtensionPreference; consinder updating fileExtensionPreference."; + $self->logger($message,'heading'); + $self->output_logfile; + die($message); + } + + # if no extension, search according to fileExtensionPreference + if (!$ext) { + $self->logger("File extension work:",'heading'); + $self->logger("latexindent called to act upon $fileName with an, as yet, unrecognised file extension;"); + $self->logger("searching for file with an extension in the following order (see fileExtensionPreference):"); + $self->logger(join("\n",@fileExtensions)); + + my $fileFound = 0; + # loop through the known file extensions (see @fileExtensions) + foreach (@fileExtensions ){ + if ( -e $fileName.$_ ) { + $self->logger("$fileName$_ found!"); + $fileName .= $_; + $self->logger("Updated fileName to $fileName"); + ${$self}{fileName} = $fileName ; + $fileFound = 1; + $ext = $_; + last; + } + } + unless($fileFound){ + $self->logger("I couldn't find a match for $fileName in fileExtensionPreference (see defaultSettings.yaml)"); + foreach (@fileExtensions ){ + $self->logger("I searched for $fileName$_"); + } + $self->logger("but couldn't find any of them."); + $self->logger("Consider updating fileExtensionPreference. Error: Exiting, no indendation done."); + $self->output_logfile; + die "I couldn't find a match for $fileName in fileExtensionPreference.\nExiting, no indendation done."; + } + } else { + # if the file has a recognised extension, check that the file exists + unless( -e $fileName ){ + my $message = "Error: I couldn't find $fileName, are you sure it exists?. No indentation done. Exiting."; + $self->logger($message); + $self->output_logfile; + die $message; + } + } + + # store the file extension + ${$self}{fileExtension} = $ext; + + # read the file into the Document body + my @lines; + open(MAINFILE, $fileName) or die "Could not open input file, $fileName"; + push(@lines,$_) while(<MAINFILE>); + close(MAINFILE); + + # the all-important step: update the body + ${$self}{body} = join("",@lines); +} +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/GetYamlSettings.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/GetYamlSettings.pm new file mode 100644 index 00000000000..4c93bc80426 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/GetYamlSettings.pm @@ -0,0 +1,471 @@ +package LatexIndent::GetYamlSettings; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use strict; +use warnings; +use LatexIndent::Switches qw/%switches $is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +use YAML::Tiny; # interpret defaultSettings.yaml and other potential settings files +use File::Basename; # to get the filename and directory path +use File::HomeDir; +use Exporter qw/import/; +our @EXPORT_OK = qw/readSettings modify_line_breaks_settings get_indentation_settings_for_this_object get_every_or_custom_value get_indentation_information get_object_attribute_for_indentation_settings alignment_at_ampersand_settings %masterSettings/; + +# Read in defaultSettings.YAML file +our $defaultSettings; + +# master yaml settings is a hash, global to this module +our %masterSettings; + +# previously found settings is a hash, global to this module +our %previouslyFoundSettings; + +sub readSettings{ + my $self = shift; + + $defaultSettings = YAML::Tiny->new; + $defaultSettings = YAML::Tiny->read( "$FindBin::RealBin/defaultSettings.yaml" ); + $self->logger("YAML settings read",'heading'); + $self->logger("Reading defaultSettings.yaml from $FindBin::RealBin/defaultSettings.yaml"); + + # if latexindent.exe is invoked from TeXLive, then defaultSettings.yaml won't be in + # the same directory as it; we need to navigate to it + if(!$defaultSettings) { + $defaultSettings = YAML::Tiny->read( "$FindBin::RealBin/../../texmf-dist/scripts/latexindent/defaultSettings.yaml"); + $self->logger("Reading defaultSettings.yaml (2nd attempt, TeXLive, Windows) from $FindBin::RealBin/../../texmf-dist/scripts/latexindent/defaultSettings.yaml"); + } + + # need to exit if we can't get defaultSettings.yaml + die "Could not open defaultSettings.yaml" if(!$defaultSettings); + + # master yaml settings is a hash, global to this module + our %masterSettings = %{$defaultSettings->[0]}; + + # scalar to read user settings + my $userSettings; + + # array to store the paths to user settings + my @absPaths; + + # we'll need the home directory a lot in what follows + my $homeDir = File::HomeDir->my_home; + + # get information about user settings- first check if indentconfig.yaml exists + my $indentconfig = "$homeDir/indentconfig.yaml"; + + # if indentconfig.yaml doesn't exist, check for the hidden file, .indentconfig.yaml + $indentconfig = "$homeDir/.indentconfig.yaml" if(! -e $indentconfig); + + # messages for indentconfig.yaml and/or .indentconfig.yaml + if ( -e $indentconfig and !$switches{onlyDefault}) { + $self->logger("Reading path information from $indentconfig"); + # if both indentconfig.yaml and .indentconfig.yaml exist + if ( -e File::HomeDir->my_home . "/indentconfig.yaml" and -e File::HomeDir->my_home . "/.indentconfig.yaml") { + $self->logger("$homeDir/.indentconfig.yaml has been found, but $indentconfig takes priority"); + } elsif ( -e File::HomeDir->my_home . "/indentconfig.yaml" ) { + $self->logger("(Alternatively $homeDir/.indentconfig.yaml can be used)"); + } elsif ( -e File::HomeDir->my_home . "/.indentconfig.yaml" ) { + $self->logger("(Alternatively $homeDir/indentconfig.yaml can be used)"); + } + + # read the absolute paths from indentconfig.yaml + $userSettings = YAML::Tiny->read( "$indentconfig" ); + + # output the contents of indentconfig to the log file + $self->logger(Dump \%{$userSettings->[0]}); + + # update the absolute paths + @absPaths = @{$userSettings->[0]->{paths}}; + } else { + if($switches{onlyDefault}) { + $self->logger("Only default settings requested, not reading USER settings from $indentconfig"); + $self->logger("Ignoring $switches{readLocalSettings} (you used the -d switch)") if($switches{readLocalSettings}); + $switches{readLocalSettings}=0; + } else { + # give the user instructions on where to put indentconfig.yaml or .indentconfig.yaml + $self->logger("Home directory is $homeDir (didn't find either indentconfig.yaml or .indentconfig.yaml)"); + $self->logger("To specify user settings you would put indentconfig.yaml here: $homeDir/indentconfig.yaml"); + $self->logger("Alternatively, you can use the hidden file .indentconfig.yaml as: $homeDir/.indentconfig.yaml"); + } + } + + # get information about LOCAL settings, assuming that $readLocalSettings exists + my $directoryName = dirname (${$self}{fileName}); + + # local settings can be separated by , + # e.g + # -l = myyaml1.yaml,myyaml2.yaml + # and in which case, we need to read them all + my @localSettings; + if($switches{readLocalSettings} =~ m/,/){ + $self->logger("Multiple localSettings found, separated by commas:",'heading'); + @localSettings = split(/,/,$switches{readLocalSettings}); + } else { + push(@localSettings,$switches{readLocalSettings}) if($switches{readLocalSettings}); + } + + # add local settings to the paths, if appropriate + foreach (@localSettings) { + if ( (-e "$directoryName/$_") and !(-z "$directoryName/$_")) { + $self->logger("Adding $directoryName/$_ to YAML read paths"); + push(@absPaths,"$directoryName/$_"); + } elsif ( !(-e "$directoryName/$_") ) { + $self->logger("WARNING yaml file not found: $directoryName/$_ not found"); + $self->logger("Proceeding without it."); + } + } + + # 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)) { + $self->logger("Reading USER settings from $settings"); + $userSettings = YAML::Tiny->read( "$settings" ); + + # if we can read userSettings + if($userSettings) { + # update the MASTER setttings to include updates from the userSettings + while(my($firstLevelKey, $firstLevelValue) = each %{$userSettings->[0]}) { + # the update approach is slightly different for hashes vs scalars/arrays + if (ref($firstLevelValue) eq "HASH") { + while(my ($secondLevelKey,$secondLevelValue) = each %{$userSettings->[0]{$firstLevelKey}}) { + if (ref $secondLevelValue eq "HASH"){ + # if masterSettings already contains a *scalar* value in secondLevelKey + # then we need to delete it (test-cases/headings-first.tex with indentRules1.yaml first demonstrated this) + if(ref $masterSettings{$firstLevelKey}{$secondLevelKey} ne "HASH"){ + $self->logger("masterSettings{$firstLevelKey}{$secondLevelKey} currently contains a *scalar* value, but it needs to be updated with a hash (see $settings); deleting the scalar") if($is_t_switch_active); + delete $masterSettings{$firstLevelKey}{$secondLevelKey} ; + } + while(my ($thirdLevelKey,$thirdLevelValue) = each %{$secondLevelValue}) { + if (ref $thirdLevelValue eq "HASH"){ + # similarly for third level + if (ref $masterSettings{$firstLevelKey}{$secondLevelKey}{$thirdLevelKey} ne "HASH"){ + $self->logger("masterSettings{$firstLevelKey}{$secondLevelKey}{$thirdLevelKey} currently contains a *scalar* value, but it needs to be updated with a hash (see $settings); deleting the scalar") if($is_t_switch_active); + delete $masterSettings{$firstLevelKey}{$secondLevelKey}{$thirdLevelKey} ; + } + while(my ($fourthLevelKey,$fourthLevelValue) = each %{$thirdLevelValue}) { + $masterSettings{$firstLevelKey}{$secondLevelKey}{$thirdLevelKey}{$fourthLevelKey} = $fourthLevelValue; + } + } else { + $masterSettings{$firstLevelKey}{$secondLevelKey}{$thirdLevelKey} = $thirdLevelValue; + } + } + } else { + $masterSettings{$firstLevelKey}{$secondLevelKey} = $secondLevelValue; + } + } + } else { + $masterSettings{$firstLevelKey} = $firstLevelValue; + } + } + + # output settings to $logfile + if($masterSettings{logFilePreferences}{showEveryYamlRead}){ + $self->logger(Dump \%{$userSettings->[0]}); + } else { + $self->logger("Not showing settings in the log file (see showEveryYamlRead and showAmalgamatedSettings)."); + } + } else { + # otherwise print a warning that we can not read userSettings.yaml + $self->logger("WARNING $settings contains invalid yaml format- not reading from it"); + } + } else { + # otherwise keep going, but put a warning in the log file + $self->logger("WARNING: $homeDir/indentconfig.yaml"); + if (-z $settings) { + $self->logger("specifies $settings but this file is EMPTY -- not reading from it"); + } else { + $self->logger("specifies $settings but this file does not exist - unable to read settings from this file"); + } + } + } + + # some users may wish to see showAmalgamatedSettings + # which details the overall state of the settings modified + # from the default in various user files + if($masterSettings{logFilePreferences}{showAmalgamatedSettings}){ + $self->logger("Amalgamated/overall settings to be used:",'heading'); + $self->logger(Dump \%masterSettings); + } + + return; +} + +sub get_indentation_settings_for_this_object{ + my $self = shift; + + # create a name for previously found settings + my $storageName = ${$self}{name}.${$self}{modifyLineBreaksYamlName}; + + # check for storage of repeated objects + if ($previouslyFoundSettings{$storageName}){ + $self->logger("Using stored settings for $storageName") if($is_t_switch_active); + } else { + my $name = ${$self}{name}; + $self->logger("Storing settings for $storageName") if($is_t_switch_active); + + # check for noAdditionalIndent and indentRules + # otherwise use defaultIndent + my $indentation = $self->get_indentation_information; + + # check for alignment at ampersand settings + $self->alignment_at_ampersand_settings; + + # check for line break settings + $self->modify_line_breaks_settings; + + # store the settings + %{${previouslyFoundSettings}{$storageName}} = ( + indentation=>$indentation, + BeginStartsOnOwnLine=>${$self}{BeginStartsOnOwnLine}, + BodyStartsOnOwnLine=>${$self}{BodyStartsOnOwnLine}, + EndStartsOnOwnLine=>${$self}{EndStartsOnOwnLine}, + EndFinishesWithLineBreak=>${$self}{EndFinishesWithLineBreak}, + ); + + # don't forget alignment settings! + ${${previouslyFoundSettings}{$storageName}}{lookForAlignDelims} = ${$self}{lookForAlignDelims} if(defined ${$self}{lookForAlignDelims}); + ${${previouslyFoundSettings}{$storageName}}{alignDoubleBackSlash} = ${$self}{alignDoubleBackSlash} if(defined ${$self}{alignDoubleBackSlash}); + ${${previouslyFoundSettings}{$storageName}}{spacesBeforeDoubleBackSlash} = ${$self}{spacesBeforeDoubleBackSlash} if(defined ${$self}{spacesBeforeDoubleBackSlash}); + + # some objects, e.g ifElseFi, can have extra assignments, e.g ElseStartsOnOwnLine + # these need to be stored as well! + foreach (@{${$self}{additionalAssignments}}){ + ${${previouslyFoundSettings}{$storageName}}{$_} = ${$self}{$_}; + } + + } + + # append indentation settings to the current object + while( my ($key,$value)= each %{${previouslyFoundSettings}{$storageName}}){ + ${$self}{$key} = $value; + } + + return; +} + +sub alignment_at_ampersand_settings{ + my $self = shift; + + # if the YamlName is, for example, optionalArguments, mandatoryArguments, heading, then we'll be looking for information about the *parent* + my $name = (defined ${$self}{nameForIndentationSettings}) ? ${$self}{nameForIndentationSettings} : ${$self}{name}; + + # check, for example, + # lookForAlignDelims: + # tabular: 1 + # or + # + # lookForAlignDelims: + # tabular: + # delims: 1 + # alignDoubleBackSlash: 1 + # spacesBeforeDoubleBackSlash: 2 + return unless ${$masterSettings{lookForAlignDelims}}{$name}; + + ## check, for example, + ## lookForAlignDelims: + ## tabular: + ## body: 1 + #return unless $self->get_object_attribute_for_indentation_settings; + + if(ref ${$masterSettings{lookForAlignDelims}}{$name} eq "HASH"){ + ${$self}{lookForAlignDelims} = (defined ${${$masterSettings{lookForAlignDelims}}{$name}}{delims} ) ? ${${$masterSettings{lookForAlignDelims}}{$name}}{delims} : 1; + ${$self}{alignDoubleBackSlash} = (defined ${${$masterSettings{lookForAlignDelims}}{$name}}{alignDoubleBackSlash} ) ? ${${$masterSettings{lookForAlignDelims}}{$name}}{alignDoubleBackSlash} : 1; + ${$self}{spacesBeforeDoubleBackSlash} = (defined ${${$masterSettings{lookForAlignDelims}}{$name}}{spacesBeforeDoubleBackSlash} ) ? ${${$masterSettings{lookForAlignDelims}}{$name}}{spacesBeforeDoubleBackSlash} : -1; + } else { + ${$self}{lookForAlignDelims} = 1; + ${$self}{alignDoubleBackSlash} = 1; + ${$self}{spacesBeforeDoubleBackSlash} = -1; + } + return; +} + +sub modify_line_breaks_settings{ + # return with undefined values unless the -m switch is active + return unless $is_m_switch_active; + + my $self = shift; + + # details to the log file + $self->logger("-m modifylinebreaks switch active, looking for settings for ${$self}{name} ",'heading') if $is_t_switch_active; + + # some objects, e.g ifElseFi, can have extra assignments, e.g ElseStartsOnOwnLine + my @toBeAssignedTo = ${$self}{additionalAssignments} ? @{${$self}{additionalAssignments}} : (); + + # the following will *definitley* be in the array, so let's add them + push(@toBeAssignedTo,("BeginStartsOnOwnLine","BodyStartsOnOwnLine","EndStartsOnOwnLine","EndFinishesWithLineBreak")); + + # we can effeciently loop through the following + foreach (@toBeAssignedTo){ + $self->get_every_or_custom_value( + toBeAssignedTo=>$_, + toBeAssignedToAlias=> ${$self}{aliases}{$_} ? ${$self}{aliases}{$_} : $_, + ); + } + return; +} + +sub get_every_or_custom_value{ + my $self = shift; + my %input = @_; + + my $toBeAssignedTo = $input{toBeAssignedTo}; + my $toBeAssignedToAlias = $input{toBeAssignedToAlias}; + + # alias + if(${$self}{aliases}{$toBeAssignedTo}){ + $self->logger("aliased $toBeAssignedTo using ${$self}{aliases}{$toBeAssignedTo}") if($is_t_switch_active); + } + + # name of the object in the modifyLineBreaks yaml (e.g environments, ifElseFi, etc) + my $YamlName = ${$self}{modifyLineBreaksYamlName}; + + # if the YamlName is either optionalArguments or mandatoryArguments, then we'll be looking for information about the *parent* + my $name = ($YamlName =~ m/Arguments/) ? ${$self}{parent} : ${$self}{name}; + + # these variables just ease the notation what follows + my $everyValue = ${${$masterSettings{modifyLineBreaks}}{$YamlName}}{$toBeAssignedToAlias}; + my $customValue = ${${${$masterSettings{modifyLineBreaks}}{$YamlName}}{$name}}{$toBeAssignedToAlias}; + + # check for the *custom* value + if (defined $customValue){ + $self->logger("$name: $toBeAssignedToAlias=$customValue, (*custom* value) adjusting $toBeAssignedTo") if($is_t_switch_active); + ${$self}{$toBeAssignedTo} = $customValue !=0 ? $customValue : undef; + } else { + # check for the *every* value + if (defined $everyValue and $everyValue != 0){ + $self->logger("$name: $toBeAssignedToAlias=$everyValue, (*every* value) adjusting $toBeAssignedTo") if($is_t_switch_active); + ${$self}{$toBeAssignedTo} = $everyValue; + } + } + return; +} + +sub get_indentation_information{ + my $self = shift; + + #************************************** + # SEARCHING ORDER: + # noAdditionalIndent *per-name* basis + # indentRules *per-name* basis + # noAdditionalIndentGlobal + # indentRulesGlobal + #************************************** + + # noAdditionalIndent can be a scalar or a hash, e.g + # + # noAdditionalIndent: + # myexample: 1 + # + # OR + # + # noAdditionalIndent: + # myexample: + # body: 1 + # optionalArguments: 1 + # mandatoryArguments: 1 + # + # specifying as a scalar with no field (e.g myexample: 1) + # will be interpreted as noAdditionalIndent for *every* + # field, so the body, optional arguments and mandatory arguments + # will *all* receive noAdditionalIndent + # + # indentRules can also be a scalar or a hash, e.g + # indentRules: + # myexample: "\t" + # + # OR + # + # indentRules: + # myexample: + # body: " " + # optionalArguments: "\t \t" + # mandatoryArguments: "" + # + # specifying as a scalar with no field will + # mean that *every* field will receive the same treatment + + # if the YamlName is, for example, optionalArguments, mandatoryArguments, heading, then we'll be looking for information about the *parent* + my $name = (defined ${$self}{nameForIndentationSettings}) ? ${$self}{nameForIndentationSettings} : ${$self}{name}; + + # if the YamlName is not optionalArguments, mandatoryArguments, heading (possibly others) then assume we're looking for 'body' + my $YamlName = $self->get_object_attribute_for_indentation_settings; + + my $indentationInformation; + foreach my $indentationAbout ("noAdditionalIndent","indentRules"){ + # check that the 'thing' is defined + if(defined ${$masterSettings{$indentationAbout}}{$name}){ + if(ref ${$masterSettings{$indentationAbout}}{$name} eq "HASH"){ + $self->logger("$indentationAbout indentation specified with multiple fields for $name, searching for $name: $YamlName (see $indentationAbout)") if $is_t_switch_active ; + $indentationInformation = ${${$masterSettings{$indentationAbout}}{$name}}{$YamlName}; + } else { + $indentationInformation = ${$masterSettings{$indentationAbout}}{$name}; + $self->logger("$indentationAbout indentation specified for $name (for *all* fields, body, optionalArguments, mandatoryArguments, afterHeading), using '$indentationInformation' (see $indentationAbout)") if $is_t_switch_active ; + } + # return, after performing an integrity check + if(defined $indentationInformation){ + if($indentationAbout eq "noAdditionalIndent" and $indentationInformation == 1){ + $self->logger("Found! Using '' (see $indentationAbout)") if $is_t_switch_active; + return q(); + } elsif($indentationAbout eq "indentRules" and $indentationInformation=~m/^\h*$/){ + $self->logger("Found! Using '$indentationInformation' (see $indentationAbout)") if $is_t_switch_active; + return $indentationInformation ; + } + } + } + } + + # gather information + $YamlName = ${$self}{modifyLineBreaksYamlName}; + + foreach my $indentationAbout ("noAdditionalIndent","indentRules"){ + # global assignments in noAdditionalIndentGlobal and/or indentRulesGlobal + my $globalInformation = $indentationAbout."Global"; + next if(!(defined ${$masterSettings{$globalInformation}}{$YamlName})); + if( ($globalInformation eq "noAdditionalIndentGlobal") and ${$masterSettings{$globalInformation}}{$YamlName}==1){ + $self->logger("$globalInformation specified for $YamlName (see $globalInformation)") if $is_t_switch_active; + return q(); + } elsif($globalInformation eq "indentRulesGlobal") { + if(${$masterSettings{$globalInformation}}{$YamlName}=~m/^\h*$/){ + $self->logger("$globalInformation specified for $YamlName (see $globalInformation)") if $is_t_switch_active; + return ${$masterSettings{$globalInformation}}{$YamlName}; + } else { + $self->logger("$globalInformation specified (${$masterSettings{$globalInformation}}{$YamlName}) for $YamlName, but it needs to only contain horizontal space -- I'm ignoring this one") if $is_t_switch_active; + } + } + } + + # return defaultIndent, by default + $self->logger("Using defaultIndent for $name") if $is_t_switch_active; + return $masterSettings{defaultIndent}; +} + +sub get_object_attribute_for_indentation_settings{ + # when looking for noAdditionalIndent or indentRules, we may need to determine + # which thing we're looking for, e.g + # + # chapter: + # body: 0 + # optionalArguments: 1 + # mandatoryArguments: 1 + # afterHeading: 0 + # + # this method returns 'body' by default, but the other objects (optionalArgument, mandatoryArgument, afterHeading) + # return their appropriate identifier. + return "body"; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Heading.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Heading.pm new file mode 100644 index 00000000000..be7ea36661d --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Heading.pm @@ -0,0 +1,240 @@ +package LatexIndent::Heading; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::Switches qw/$is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_heading construct_headings_levels/; +our $headingCounter; +our @headingsRegexpArray; +our $allHeadingsRegexp = q(); + +sub construct_headings_levels{ + my $self = shift; + + # grab the heading levels + my %headingsLevels = %{$masterSettings{indentAfterHeadings}}; + + # delete the values that have indentAfterThisHeading set to 0 + while( my ($headingName,$headingInfo)= each %headingsLevels){ + if(!${$headingsLevels{$headingName}}{indentAfterThisHeading}){ + $self->logger("Not indenting after $headingName (see indentAfterThisHeading)",'heading') if $is_t_switch_active; + delete $headingsLevels{$headingName}; + } else { + # *all heading* regexp, remembering put starred headings at the front of the regexp + if($headingName =~ m/\*/){ + $self->logger("Putting $headingName at the beginning of the allHeadings regexp, as it contains a *"); + $allHeadingsRegexp = $headingName.($allHeadingsRegexp eq '' ?q():"|$allHeadingsRegexp"); + } else { + $self->logger("Putting $headingName at the END of the allHeadings regexp, as it contains a *"); + $allHeadingsRegexp .= ($allHeadingsRegexp eq '' ?q():"|").$headingName ; + } + } + } + + # check for a * in the name + $allHeadingsRegexp =~ s/\*/\\\*/g; + + # sort the file extensions by preference + my @sortedByLevels = sort { ${$headingsLevels{$a}}{level} <=> $headingsLevels{$b}{level} } keys(%headingsLevels); + + # it could be that @sortedByLevels is empty; + return if !@sortedByLevels; + + $self->logger("All headings regexp: $allHeadingsRegexp",'heading'); + $self->logger("Now to construct headings regexp for each level:",'heading'); + + # loop through the levels, and create a regexp for each (min and max values are the first and last values respectively from sortedByLevels) + for(my $i = ${$headingsLevels{$sortedByLevels[0]}}{level}; $i <= ${$headingsLevels{$sortedByLevels[-1]}}{level}; $i++ ){ + # level regexp + my @tmp = grep { ${$headingsLevels{$_}}{level} == $i } keys %headingsLevels; + if(@tmp){ + my $headingsAtThisLevel = q(); + foreach(@tmp){ + # put starred headings at the front of the regexp + if($_ =~ m/\*/){ + $self->logger("Putting $_ at the beginning of this regexp, as it contains a *"); + $headingsAtThisLevel = $_.($headingsAtThisLevel eq '' ?q():"|$headingsAtThisLevel"); + } else { + $self->logger("Putting $_ at the END of this regexp, as it contains a *"); + $headingsAtThisLevel .= ($headingsAtThisLevel eq '' ?q():"|").$_ ; + } + } + + # make the stars escaped correctly + $headingsAtThisLevel =~ s/\*/\\\*/g; + push(@headingsRegexpArray,$headingsAtThisLevel); + $self->logger("Heading level regexp for level $i will contain: $headingsAtThisLevel"); + } + } + } + +sub find_heading{ + + # if there are no headings regexps, there's no point going any further + return if!@headingsRegexpArray; + + my $self = shift; + + # otherwise loop through the headings regexp + $self->logger("Searching for special begin/end (see specialBeginEnd)"); + + # loop through each headings match; note that we need to + # do it in *reverse* so as to ensure that the lower level headings get matched first of all + foreach(reverse(@headingsRegexpArray)){ + + # the regexp + my $headingRegExp = qr/ + ( + \\($_) # name stored into $2 + ) # beginning bit into $1 + ( + .*? + ) # body into $3 + (\R*)? # linebreaks at end of body into $4 + ((?:\\(?:$allHeadingsRegexp))|$) # up to another heading, or else the end of the file + /sx; + + while(${$self}{body} =~ m/$headingRegExp/){ + + # log file output + $self->logger("heading found: $2",'heading'); + + ${$self}{body} =~ s/ + $headingRegExp + / + # create a new heading object + my $headingObject = LatexIndent::Heading->new(begin=>q(), + body=>$1.$3, + end=>q(), + afterbit=>($4?$4:q()).($5?$5:q()), + name=>$2.":heading", + parent=>$2, + nameForIndentationSettings=>$2, + linebreaksAtEnd=>{ + begin=>0, + body=>0, + end=>0, + }, + modifyLineBreaksYamlName=>"afterHeading", + endImmediatelyFollowedByComment=>0, + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($headingObject); + ${@{${$self}{children}}[-1]}{replacementText}; + /xse; + } + } +} + +sub get_replacement_text{ + my $self = shift; + + # the replacement text for a heading (chapter, section, etc) needs to put the trailing part back in + $self->logger("Custom replacement text routine for heading ${$self}{name}"); + ${$self}{replacementText} = ${$self}{id}.${$self}{afterbit}; + delete ${$self}{afterbit}; +} + +sub create_unique_id{ + my $self = shift; + + $headingCounter++; + + ${$self}{id} = "$tokens{heading}$headingCounter"; + return; +} + +sub adjust_replacement_text_line_breaks_at_end{ + return; +} + +sub get_object_attribute_for_indentation_settings{ + # when looking for noAdditionalIndent or indentRules, we may need to determine + # which thing we're looking for, e.g + # + # chapter: + # body: 0 + # optionalArguments: 1 + # mandatoryArguments: 1 + # afterHeading: 0 + # + # this method returns 'body' by default, but the other objects (optionalArgument, mandatoryArgument, afterHeading) + # return their appropriate identifier. + my $self = shift; + + return ${$self}{modifyLineBreaksYamlName}; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # search for commands, keys, named grouping braces + $self->find_commands_or_key_equals_values_braces; + + # we need to transfer the details from the modifyLineBreaks of the command + # child object to the heading object. + # + # for example, if we have + # + # \chapter{some heading here} + # + # and we want to modify the linebreak before the \chapter command using, for example, + # + # commands: + # CommandStartsOnOwnLine: 1 + # + # then we need to transfer this information to the heading object + if($is_m_switch_active){ + $self->logger("Searching for linebreak preferences immediately infront of ${$self}{parent}",'heading'); + foreach(@{${$self}{children}}){ + if(${$_}{name} eq ${$self}{parent}){ + $self->logger("Named child found: ${$_}{name}"); + if(defined ${$_}{BeginStartsOnOwnLine}){ + $self->logger("Transferring information from ${$_}{id} (${$_}{name}) to ${$self}{id} (${$self}{name}) for BeginStartsOnOwnLine"); + ${$self}{BeginStartsOnOwnLine} = ${$_}{BeginStartsOnOwnLine}; + } else { + $self->logger("No information found in ${$_}{name} for BeginStartsOnOwnLine"); + } + last; + } + } + } + + # search for special begin/end + $self->find_special; + + return; +} + +sub add_surrounding_indentation_to_begin_statement{ + # almost all of the objects add surrounding indentation to the 'begin' statements, + # but some (e.g HEADING) have their own method + my $self = shift; + + $self->logger("Adding surrounding indentation after (empty, by design!) begin statement of ${$self}{name} (${$self}{id})"); + ${$self}{begin} .= ${$self}{surroundingIndentation}; # add indentation + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/HiddenChildren.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/HiddenChildren.pm new file mode 100644 index 00000000000..594e7dbd14e --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/HiddenChildren.pm @@ -0,0 +1,190 @@ +package LatexIndent::HiddenChildren; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use LatexIndent::Tokens qw/%tokens/; +use Data::Dumper; +use Exporter qw/import/; +our @EXPORT_OK = qw/find_surrounding_indentation_for_children update_family_tree get_family_tree check_for_hidden_children %familyTree/; + +# hiddenChildren can be stored in a global array, it doesn't matter what level they're at +our %familyTree; +our %allChildren; + + +#---------------------------------------------------------- +# Discussion surrounding hidden children +# +# Consider the following latex code +# +# \begin{one} +# body of one +# body of one +# body of one +# \begin{two} +# body of two +# body of two +# body of two +# body of two +# \end{two} +# \end{one} +# +# From the visual perspective, we might say that <one> and <two> are *nested* children; +# from the persepective of latexindent.pl, however, they actually have *the same level*. +# +# Graphically, you might represent it as follows +# +# * +# / \ +# / \ +# / \ +# O O +# +# where * represents the 'root' document object, and each 'O' is an environment object; the +# first one, on the left, represents <two> and the second one, on the right, represents <one>. +# (Remember that the environment regexp does not allow \begin within its body.) +# +# When processing the document, <one> will be processed *before* <two>. Furthermore, because +# <one> and <two> are at the same level, they are not *natural* ancestors of each other; as such, +# we say that <two> is a *hidden* child, and that its 'adopted' ancestor is <one>. +# +# We need to go to a lot of effort to make sure that <two> knows about its ancestors and its +# surrounding indentation (<one> in this case). The subroutines in this file do that effort. +#---------------------------------------------------------- + +sub find_surrounding_indentation_for_children{ + my $self = shift; + + # output to logfile + $self->logger("FamilyTree before update:",'heading') if $is_t_switch_active; + $self->logger(Dumper(\%familyTree)) if($is_t_switch_active); + + # update the family tree with ancestors + $self->update_family_tree; + + # output information to the logfile + $self->logger("FamilyTree after update:",'heading') if $is_t_switch_active; + $self->logger(Dumper(\%familyTree)) if($is_t_switch_active); + + while( my ($idToSearch,$ancestorToSearch) = each %familyTree){ + $self->logger("Hidden child ID: ,$idToSearch, here are its ancestors:",'heading') if $is_t_switch_active; + foreach(@{${$ancestorToSearch}{ancestors}}){ + $self->logger("ID: ${$_}{ancestorID}") if($is_t_switch_active); + my $tmpIndentation = ref(${$_}{ancestorIndentation}) eq 'SCALAR'?${${$_}{ancestorIndentation}}:${$_}{ancestorIndentation}; + $tmpIndentation = $tmpIndentation ? $tmpIndentation : q(); + $self->logger("indentation: '$tmpIndentation'") if($is_t_switch_active); + } + } + + return; +} + +sub update_family_tree{ + my $self = shift; + + # loop through the hash + $self->logger("Updating FamilyTree...",'heading') if $is_t_switch_active; + while( my ($idToSearch,$ancestorToSearch)= each %familyTree){ + foreach(@{${$ancestorToSearch}{ancestors}}){ + my $ancestorID = ${$_}{ancestorID}; + $self->logger("current ID: $idToSearch, ancestor: $ancestorID") if($is_t_switch_active); + if($familyTree{$ancestorID}){ + $self->logger("$ancestorID is a key within familyTree, grabbing its ancestors") if($is_t_switch_active); + my $naturalAncestors = q(); + foreach(@{${$familyTree{$idToSearch}}{ancestors}}){ + $naturalAncestors .= "---".${$_}{ancestorID} if(${$_}{type} eq "natural"); + } + foreach(@{${$familyTree{$ancestorID}}{ancestors}}){ + $self->logger("ancestor of *hidden* child: ${$_}{ancestorID}") if($is_t_switch_active); + my $newAncestorId = ${$_}{ancestorID}; + my $type; + if($naturalAncestors =~ m/$ancestorID/){ + $type = "natural"; + } else { + $type = "adopted"; + } + my $matched = grep { $_->{ancestorID} eq $newAncestorId } @{${$familyTree{$idToSearch}}{ancestors}}; + push(@{${$familyTree{$idToSearch}}{ancestors}},{ancestorID=>${$_}{ancestorID},ancestorIndentation=>${$_}{ancestorIndentation},type=>$type}) unless($matched); + } + } else { + my $naturalAncestors = q(); + foreach(@{${$familyTree{$idToSearch}}{ancestors}}){ + $naturalAncestors .= "---".${$_}{ancestorID} if(${$_}{type} eq "natural"); + } + $self->logger("natural ancestors of $ancestorID: $naturalAncestors") if($is_t_switch_active); + foreach(@{${$allChildren{$ancestorID}}{ancestors}}){ + my $newAncestorId = ${$_}{ancestorID}; + my $type; + if($naturalAncestors =~ m/$newAncestorId/){ + $type = "natural"; + } else { + $type = "adopted"; + } + my $matched = grep { $_->{ancestorID} eq $newAncestorId } @{${$familyTree{$idToSearch}}{ancestors}}; + unless($matched){ + $self->logger("ancestor of UNHIDDEN child: ${$_}{ancestorID}") if($is_t_switch_active); + push(@{${$familyTree{$idToSearch}}{ancestors}},{ancestorID=>${$_}{ancestorID},ancestorIndentation=>${$_}{ancestorIndentation},type=>$type}); + } + } + } + } + } + +} + +sub check_for_hidden_children{ + + my $self = shift; + + # if there are no hidden children, then exit + return if ${$self}{body} !~ m/$tokens{beginOfToken}/; + + # grab the matches + my @matched = (${$self}{body} =~ /((?:$tokens{ifelsefiSpecial})?$tokens{beginOfToken}.[-a-z0-9]+?$tokens{endOfToken})/ig); + + # log file + $self->logger("Hidden children check") if $is_t_switch_active; + $self->logger(join("|",@matched)) if $is_t_switch_active; + + my $naturalAncestors = ${$self}{naturalAncestors}; + + # loop through the hidden children + foreach my $match (@matched){ + # update the family tree with ancestors of self + if(${$self}{ancestors}){ + foreach(@{${$self}{ancestors}}){ + my $newAncestorId = ${$_}{ancestorID}; + unless (grep { $_->{ancestorID} eq $newAncestorId } @{${$familyTree{$match}}{ancestors}}){ + my $type = ($naturalAncestors =~ m/${$_}{ancestorID}/ ) ? "natural" : "adopted"; + $self->logger("Adding ${$_}{ancestorID} to the $type family tree of $match") if($is_t_switch_active); + push(@{$familyTree{$match}{ancestors}},{ancestorID=>${$_}{ancestorID},ancestorIndentation=>${$_}{ancestorIndentation},type=>$type}); + } + } + } + + # update the family tree with self + unless (grep { $_->{ancestorID} eq ${$self}{id}} @{${$familyTree{$match}}{ancestors}}){ + my $type = ($naturalAncestors =~ m/${$self}{id}/ ) ? "natural" : "adopted"; + $self->logger("Adding ${$self}{id} to the $type family tree of hiddenChild $match") if($is_t_switch_active); + push(@{$familyTree{$match}{ancestors}},{ancestorID=>${$self}{id},ancestorIndentation=>${$self}{indentation},type=>$type}); + } + } + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/HorizontalWhiteSpace.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/HorizontalWhiteSpace.pm new file mode 100644 index 00000000000..360a8077022 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/HorizontalWhiteSpace.pm @@ -0,0 +1,62 @@ +package LatexIndent::HorizontalWhiteSpace; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Exporter qw/import/; +our @EXPORT_OK = qw/remove_trailing_whitespace remove_leading_space/; + +sub remove_trailing_whitespace{ + my $self = shift; + my %input = @_; + + # this method can be called before the indendation, and after, depending upon the input + if($input{when} eq "before"){ + return unless(${$masterSettings{removeTrailingWhitespace}}{beforeProcessing}); + $self->logger("Removing trailing white space *before* the document is processed (see removeTrailingWhitespace: beforeProcessing)",'heading') if $is_t_switch_active; + } elsif($input{when} eq "after"){ + return unless(${$masterSettings{removeTrailingWhitespace}}{afterProcessing}); + $self->logger("Removing trailing white space *after* the document is processed (see removeTrailingWhitespace: afterProcessing)",'heading') if $is_t_switch_active; + } else { + return; + } + + ${$self}{body} =~ s/ + \h+ # followed by possible horizontal space + $ # up to the end of a line + //xsmg; + + $self->logger("Processed body, *$input{when}* indentation (${$self}{name}):") if($is_t_switch_active); + $self->logger(${$self}{body}) if($is_t_switch_active); + +} + +sub remove_leading_space{ + my $self = shift; + $self->logger("Removing leading space from ${$self}{name} (verbatim/noindentblock already accounted for)",'heading') if $is_t_switch_active; + ${$self}{body} =~ s/ + ( + ^ # beginning of the line + \h* # with 0 or more horizontal spaces + )? # possibly + //mxg; + return; +} + + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/IfElseFi.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/IfElseFi.pm new file mode 100755 index 00000000000..e1ac17b3613 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/IfElseFi.pm @@ -0,0 +1,276 @@ +package LatexIndent::IfElseFi; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_ifelsefi/; +our %previouslyFoundSettings; +our $ifElseFiCounter; + +# store the regular expresssion for matching and replacing the \if...\else...\fi statements +# note: we search for \else separately in an attempt to keep this regexp a little more managable +our $ifElseFiRegExp = qr/ + ( + \\ + (@?if[a-zA-Z@]*?) + \h* + (\R*) + ) # begin statement, e.g \ifnum, \ifodd + ( + \\(?!if)|\R|\h|\#|!-! # up until a \\, linebreak # or !-!, which is + ) # part of the tokens used for latexindent + ( + (?: + (?!\\if). + )*? # body, which can't include another \if + ) + (\R*) # linebreaks after body + ( + \\fi(?![a-zA-Z]) # \fi statement + ) + (\h*) # 0 or more horizontal spaces + (\R)? # linebreaks after \fi +/sx; + + +sub indent{ + my $self = shift; + + # determine the surrounding and current indentation + $self->determine_total_indentation; + + # line break checks after \if statement, can get messy if we + # have, for example + # \ifnum + # something + # which might be changed into + # \ifnumsomething + # which is undeserible + if (defined ${$self}{BodyStartsOnOwnLine} + and ${$self}{BodyStartsOnOwnLine}==-1 + and ${$self}{body} !~ m/^(\h|\\|(?:!-!))/s + ){ + ${$self}{begin} .= " "; + } + + # indent the body + $self->indent_body; + + # calculate and grab the surrounding indentation for the \else statement adjustment + $self->get_surrounding_indentation; + my $surroundingIndentation = ${$self}{surroundingIndentation}?${$self}{surroundingIndentation}:q(); + + if(${$self}{elsePresent} and ${$self}{linebreaksAtEnd}{ifbody}){ + $self->logger("Adding surrounding indentation to \\else statement ('$surroundingIndentation')")if $is_t_switch_active; + ${$self}{body} =~ s/\h*\\else/$surroundingIndentation\\else/; + $self->logger("Body (${$self}{name}) after \\else adjustment:\n${$self}{body}") if $is_t_switch_active; + } + + # indent the end statement + $self->indent_end_statement; + + # wrap-up statement + $self->wrap_up_statement; + + # line break checks *after* \end{statement} + if (defined ${$self}{EndFinishesWithLineBreak} + and ${$self}{EndFinishesWithLineBreak}==-1 + ) { + # add a single horizontal space after the child id, otherwise we can end up + # with things like + # before: + # \fi + # text + # after: + # \fitext + $self->logger("Adding a single space after \\fi statement (otherwise \\fi can be comined with next line of text in an unwanted way)",'heading') if $is_t_switch_active; + ${$self}{end} =${$self}{end}." "; + } + + return $self; +} + +sub find_ifelsefi{ + my $self = shift; + + while( ${$self}{body} =~ m/$ifElseFiRegExp\h*($trailingCommentRegExp)?/){ + + ${$self}{body} =~ s/ + $ifElseFiRegExp(\h*)($trailingCommentRegExp)? + / + # log file output + $self->logger("IfElseFi found: $2",'heading')if $is_t_switch_active; + + # create a new IfElseFi object + my $ifElseFi = LatexIndent::IfElseFi->new(begin=>$1.(($4 eq "\n" and !$3)?"\n":q()), + name=>$2, + # if $4 is a line break, don't count it twice (it will already be in 'begin') + body=>($4 eq "\n") ? $5.$6 : $4.$5.$6, + end=>$7, + linebreaksAtEnd=>{ + begin=>(($4 eq "\n")||$3)?1:0, + body=>$6?1:0, + end=>$9?1:0, + }, + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"IfStartsOnOwnLine", + # end statements + EndStartsOnOwnLine=>"FiStartsOnOwnLine", + # after end statements + EndFinishesWithLineBreak=>"FiFinishesWithLineBreak", + }, + elsePresent=>0, + modifyLineBreaksYamlName=>"ifElseFi", + additionalAssignments=>["ElseStartsOnOwnLine","ElseFinishesWithLineBreak"], + endImmediatelyFollowedByComment=>$9?0:($11?1:0), + horizontalTrailingSpace=>$8?$8:q(), + ); + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($ifElseFi); + ${@{${$self}{children}}[-1]}{replacementText}.($10?$10:q()).($11?$11:q()); + /xse; + + } + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # check for existence of \else statement, and associated line break information + $self->check_for_else_statement; + + # search for headings (important to do this before looking for commands!) + $self->find_heading; + + # search for commands, keys, named grouping braces + $self->find_commands_or_key_equals_values_braces; + + # search for arguments + $self->find_opt_mand_arguments; + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for special begin/end + $self->find_special; + +} + +sub create_unique_id{ + my $self = shift; + + $ifElseFiCounter++; + + ${$self}{id} = "$tokens{ifelsefi}$ifElseFiCounter"; + return; +} + +sub check_for_else_statement{ + my $self = shift; + $self->logger("Looking for \\else statement (${$self}{name})",'heading') if $is_t_switch_active; + if(${$self}{body} =~ m/ + (\R*) # possible line breaks before \else statement + \\else + \h* # possible horizontal space + (\R*) # possible line breaks after \else statement + /x){ + $self->logger("found \\else statement, storing line break information:") if($is_t_switch_active); + + # linebreaks *before* \else statement + ${$self}{linebreaksAtEnd}{ifbody} = $1?1:0; + $self->logger("linebreaksAtEnd of ifbody: ${$self}{linebreaksAtEnd}{ifbody}") if($is_t_switch_active); + + # linebreaks *after* \else statement + ${$self}{linebreaksAtEnd}{else} = $2?1:0; + $self->logger("linebreaksAtEnd of else: ${$self}{linebreaksAtEnd}{else}") if($is_t_switch_active); + ${$self}{elsePresent}=1; + + # check that \else isn't the first thing in body + if(${$self}{body} =~ m/^\\else/s and ${$self}{linebreaksAtEnd}{begin}){ + ${$self}{linebreaksAtEnd}{ifbody} = 1; + $self->logger("\\else *begins* the ifbody, linebreaksAtEnd of ifbody: ${$self}{linebreaksAtEnd}{ifbody}") if($is_t_switch_active); + } + + # check if -m switch is active + return unless $is_m_switch_active; + + # possibly modify line break *before* \else statement + if(defined ${$self}{ElseStartsOnOwnLine}){ + if(${$self}{ElseStartsOnOwnLine}>=1 and !${$self}{linebreaksAtEnd}{ifbody}){ + # by default, assume that no trailing comment token is needed + my $trailingCommentToken = q(); + if(${$self}{ElseStartsOnOwnLine}==2){ + $self->logger("Adding a % immediately before else statement of ${$self}{name} (ElseStartsOnOwnLine==2)") if $is_t_switch_active; + $trailingCommentToken = "%".$self->add_comment_symbol; + } + + # add a line break after ifbody, if appropriate + $self->logger("Adding a linebreak before the \\else statement (see ElseStartsOnOwnLine)"); + ${$self}{body} =~ s/\\else/$trailingCommentToken\n\\else/s; + ${$self}{linebreaksAtEnd}{ifbody} = 1; + } elsif (${$self}{ElseStartsOnOwnLine}==-1 and ${$self}{linebreaksAtEnd}{ifbody}){ + # remove line break *after* ifbody, if appropriate + $self->logger("Removing linebreak before \\else statement (see ElseStartsOnOwnLine)"); + ${$self}{body} =~ s/\R*(\h*)\\else/$1\\else/sx; + ${$self}{linebreaksAtEnd}{ifbody} = 0; + } + } + + # possibly modify line break *before* \else statement + if(defined ${$self}{ElseFinishesWithLineBreak}){ + if(${$self}{ElseFinishesWithLineBreak}>=1 and !${$self}{linebreaksAtEnd}{else}){ + # by default, assume that no trailing comment token is needed + my $trailingCommentToken = q(); + if(${$self}{ElseFinishesWithLineBreak}==2){ + return if(${$self}{body} =~ m/\\else\h*$trailingCommentRegExp/s); + $self->logger("Adding a % immediately after else statement of ${$self}{name} (ElseFinishesWithLineBreak==2)") if $is_t_switch_active; + $trailingCommentToken = "%".$self->add_comment_symbol; + } + + # add a line break after else, if appropriate + $self->logger("Adding a linebreak after the \\else statement (see ElseFinishesWithLineBreak)")if $is_t_switch_active; + ${$self}{body} =~ s/\\else\h*/\\else$trailingCommentToken\n/s; + ${$self}{linebreaksAtEnd}{else} = 1; + } elsif (${$self}{ElseFinishesWithLineBreak}==-1 and ${$self}{linebreaksAtEnd}{else}){ + # remove line break *after* else, if appropriate, + # note the space so that, for example, + # \else + # some text + # becomes + # \else some text + # and not + # \elsesome text + $self->logger("Removing linebreak after \\else statement (see ElseFinishesWithLineBreak)")if $is_t_switch_active; + ${$self}{body} =~ s/\\else\h*\R*/\\else /sx; + ${$self}{linebreaksAtEnd}{else} = 0; + } + } + + return; + } else { + $self->logger("\\else statement not found") if($is_t_switch_active); + } +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Indent.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Indent.pm new file mode 100644 index 00000000000..600aed5040f --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Indent.pm @@ -0,0 +1,371 @@ +package LatexIndent::Indent; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::Switches qw/$is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +use LatexIndent::HiddenChildren qw/%familyTree/; +use Data::Dumper; +use Exporter qw/import/; +our @EXPORT_OK = qw/indent wrap_up_statement determine_total_indentation indent_begin indent_body indent_end_statement final_indentation_check push_family_tree_to_indent get_surrounding_indentation indent_children_recursively check_for_blank_lines_at_beginning put_blank_lines_back_in_at_beginning add_surrounding_indentation_to_begin_statement/; +our %familyTree; + +sub indent{ + my $self = shift; + + # determine the surrounding and current indentation + $self->determine_total_indentation; + + # indent the begin statement + $self->indent_begin; + + # indent the body + $self->indent_body; + + # indent the end statement + $self->indent_end_statement; + + # output the completed object to the log file + $self->logger("Complete indented object (${$self}{name}) after indentation:\n${$self}{begin}${$self}{body}${$self}{end}") if $is_t_switch_active; + + # wrap-up statement + $self->wrap_up_statement; + return $self; +} + +sub wrap_up_statement{ + my $self = shift; + $self->logger("Finished indenting ${$self}{name}",'heading') if $is_t_switch_active; + return $self; + } + +sub determine_total_indentation{ + my $self = shift; + + # calculate and grab the surrounding indentation + $self->get_surrounding_indentation; + + # logfile information + my $surroundingIndentation = ${$self}{surroundingIndentation}; + $self->logger("indenting object ${$self}{name}") if($is_t_switch_active); + (my $during = $surroundingIndentation) =~ s/\t/TAB/g; + $self->logger("indentation *surrounding* object: '$during'") if($is_t_switch_active); + ($during = ${$self}{indentation}) =~ s/\t/TAB/g; + $self->logger("indentation *of* object: '$during'") if($is_t_switch_active); + ($during = $surroundingIndentation.${$self}{indentation}) =~ s/\t/TAB/g; + $self->logger("*total* indentation to be added: '$during'") if($is_t_switch_active); + + # form the total indentation of the object + ${$self}{indentation} = $surroundingIndentation.${$self}{indentation}; + +} + +sub get_surrounding_indentation{ + my $self = shift; + + my $surroundingIndentation = q(); + + if($familyTree{${$self}{id}}){ + $self->logger("Adopted ancestors found!") if($is_t_switch_active); + foreach(@{${$familyTree{${$self}{id}}}{ancestors}}){ + if(${$_}{type} eq "adopted"){ + my $newAncestorId = ${$_}{ancestorID}; + $self->logger("ancestor ID: $newAncestorId, adding indentation of $newAncestorId to surroundingIndentation of ${$self}{id}") if($is_t_switch_active); + $surroundingIndentation .= ref(${$_}{ancestorIndentation}) eq 'SCALAR' + ? + (${${$_}{ancestorIndentation}}?${${$_}{ancestorIndentation}}:q()) + : + (${$_}{ancestorIndentation}?${$_}{ancestorIndentation}:q()); + } + } + } + ${$self}{surroundingIndentation} = $surroundingIndentation; + +} + +sub indent_begin{ + # for most objects, the begin statement is just one line, but there are exceptions, e.g KeyEqualsValuesBraces + return; +} + +sub indent_body{ + my $self = shift; + + # grab the indentation of the object + my $indentation = ${$self}{indentation}; + + # output to the logfile + $self->logger("Body (${$self}{name}) before indentation:\n${$self}{body}") if $is_t_switch_active; + + # last minute check for modified bodyLineBreaks + $self->count_body_line_breaks if $is_m_switch_active; + + # some objects need to check for blank line tokens at the beginning + $self->check_for_blank_lines_at_beginning if $is_m_switch_active; + + # some objects can format their body to align at the & character + $self->align_at_ampersand if ${$self}{lookForAlignDelims}; + + # body indendation + if(${$self}{linebreaksAtEnd}{begin}==1){ + if(${$self}{body} =~ m/^\h*$/s){ + $self->logger("Body of ${$self}{name} is empty, not applying indentation") if $is_t_switch_active; + } else { + # put any existing horizontal space after the current indentation + $self->logger("Entire body of ${$self}{name} receives indendentation") if $is_t_switch_active; + ${$self}{body} =~ s/^(\h*)/$indentation$1/mg; # add indentation + } + } elsif(${$self}{linebreaksAtEnd}{begin}==0 and ${$self}{bodyLineBreaks}>0) { + if(${$self}{body} =~ m/ + (.*?) # content of first line + \R # first line break + (.*$) # rest of body + /sx){ + my $bodyFirstLine = $1; + my $remainingBody = $2; + $self->logger("first line of body: $bodyFirstLine",'heading') if $is_t_switch_active; + $self->logger("remaining body (before indentation):\n'$remainingBody'") if($is_t_switch_active); + + # add the indentation to all the body except first line + $remainingBody =~ s/^/$indentation/mg unless($remainingBody eq ''); # add indentation + $self->logger("remaining body (after indentation):\n$remainingBody'") if($is_t_switch_active); + + # put the body back together + ${$self}{body} = $bodyFirstLine."\n".$remainingBody; + } + } + + # if the routine check_for_blank_lines_at_beginning has been called, then the following routine + # puts blank line tokens back in + $self->put_blank_lines_back_in_at_beginning if $is_m_switch_active; + + # the final linebreak can be modified by a child object; see test-cases/commands/figureValign-mod5.tex, for example + if($is_m_switch_active and defined ${$self}{linebreaksAtEnd}{body} and ${$self}{linebreaksAtEnd}{body}==1 and ${$self}{body} !~ m/\R$/){ + $self->logger("Updating body for ${$self}{name} to contain a linebreak at the end (linebreaksAtEnd is 1, but there isn't currently a linebreak)") if($is_t_switch_active); + ${$self}{body} .= "\n"; + } + + # output to the logfile + $self->logger("Body (${$self}{name}) after indentation:\n${$self}{body}") if $is_t_switch_active; + return $self; +} + +sub check_for_blank_lines_at_beginning{ + # some objects need this routine + return; +} + +sub put_blank_lines_back_in_at_beginning{ + # some objects need this routine + return; +} + +sub indent_end_statement{ + my $self = shift; + my $surroundingIndentation = (${$self}{surroundingIndentation} and $familyTree{${$self}{id}}) + ? + (ref(${$self}{surroundingIndentation}) eq 'SCALAR'?${${$self}{surroundingIndentation}}:${$self}{surroundingIndentation}) + :q(); + + # end{statement} indentation, e.g \end{environment}, \fi, }, etc + if(${$self}{linebreaksAtEnd}{body}){ + ${$self}{end} =~ s/^\h*/$surroundingIndentation/mg; # add indentation + $self->logger("Adding surrounding indentation to ${$self}{end} (${$self}{name}: '$surroundingIndentation')") if($is_t_switch_active); + } + return $self; +} + +sub final_indentation_check{ + # problem: + # if a tab is appended to spaces, it will look different + # from spaces appended to tabs (see test-cases/items/spaces-and-tabs.tex) + # solution: + # move all of the tabs to the beginning of ${$self}{indentation} + # notes; + # this came to light when studying test-cases/items/items1.tex + + my $self = shift; + + my $indentation; + my $numberOfTABS; + my $after; + ${$self}{body} =~ s/ + ^((\h*|\t*)((\h+)(\t+))+) + / + # fix the indentation + $indentation = $1; + + # count the number of tabs + $numberOfTABS = () = $indentation=~ \/\t\/g; + $self->logger("Number of tabs: $numberOfTABS") if($is_t_switch_active); + + # log the after + ($after = $indentation) =~ s|\t||g; + $after = "TAB"x$numberOfTABS.$after; + $self->logger("Indentation after: '$after'") if($is_t_switch_active); + ($indentation = $after) =~s|TAB|\t|g; + $indentation; + /xsmeg; + +} + +sub indent_children_recursively{ + my $self = shift; + + unless(defined ${$self}{children}) { + $self->logger("No child objects (${$self}{name})") if $is_t_switch_active; + return; + } + + $self->logger('Pre-processed body:','heading') if $is_t_switch_active; + $self->logger(${$self}{body}) if($is_t_switch_active); + + # send the children through this indentation routine recursively + if(defined ${$self}{children}){ + foreach my $child (@{${$self}{children}}){ + $self->logger("Indenting child objects on ${$child}{name}") if $is_t_switch_active; + $child->indent_children_recursively; + } + } + + $self->logger("Replacing ids with begin, body, and end statements:",'heading') if $is_t_switch_active; + + # loop through document children hash + while( scalar (@{${$self}{children}}) > 0 ){ + my $index = 0; + # we work through the array *in order* + foreach my $child (@{${$self}{children}}){ + $self->logger("Searching ${$self}{name} for ${$child}{id}...",'heading') if $is_t_switch_active; + if(${$self}{body} =~ m/${$child}{id}/s){ + # we only care if id is first non-white space character + # and if followed by line break + # if m switch is active + my $IDFirstNonWhiteSpaceCharacter = 0; + my $IDFollowedImmediatelyByLineBreak = 0; + + # update the above two, if necessary + if ($is_m_switch_active){ + $IDFirstNonWhiteSpaceCharacter = (${$self}{body} =~ m/^${$child}{id}/m + or + ${$self}{body} =~ m/^\h\h*${$child}{id}/m + ) ?1:0; + $IDFollowedImmediatelyByLineBreak = (${$self}{body} =~ m/${$child}{id}\h*\R*/m) ?1:0; + } + + # log file info + $self->logger("${$child}{id} found!") if($is_t_switch_active); + $self->logger("Indenting ${$child}{name} (id: ${$child}{id})",'heading') if $is_t_switch_active; + $self->logger("looking up indentation scheme for ${$child}{name}") if($is_t_switch_active); + + # line break checks *after* <end statement> + if (defined ${$child}{EndFinishesWithLineBreak} + and ${$child}{EndFinishesWithLineBreak}==-1 + and $IDFollowedImmediatelyByLineBreak) { + # remove line break *after* <end statement>, if appropriate + my $EndStringLogFile = ${$child}{aliases}{EndFinishesWithLineBreak}||"EndFinishesWithLineBreak"; + $self->logger("Removing linebreak after ${$child}{end} (see $EndStringLogFile)") if $is_t_switch_active; + ${$self}{body} =~ s/${$child}{id}(\h*)?(\R|\h)*/${$child}{id}$1/s; + ${$child}{linebreaksAtEnd}{end} = 0; + } + + # perform indentation + $child->indent; + + # surrounding indentation is now up to date + my $surroundingIndentation = (${$child}{surroundingIndentation} and ${$child}{hiddenChildYesNo}) + ? + (ref(${$child}{surroundingIndentation}) eq 'SCALAR'?${${$child}{surroundingIndentation}}:${$child}{surroundingIndentation}) + :q(); + + # line break checks before <begin statement> + if(defined ${$child}{BeginStartsOnOwnLine}){ + my $BeginStringLogFile = ${$child}{aliases}{BeginStartsOnOwnLine}||"BeginStartsOnOwnLine"; + if(${$child}{BeginStartsOnOwnLine}>=1 and !$IDFirstNonWhiteSpaceCharacter){ + # by default, assume that no trailing comment token is needed + my $trailingCommentToken = q(); + if(${$child}{BeginStartsOnOwnLine}==2){ + $self->logger("Removing space immediately before ${$child}{id}, in preparation for adding % ($BeginStringLogFile == 2)") if $is_t_switch_active; + ${$self}{body} =~ s/\h*${$child}{id}/${$child}{id}/s; + $self->logger("Adding a % at the end of the line that ${$child}{begin} is on, then a linebreak ($BeginStringLogFile == 2)") if $is_t_switch_active; + $trailingCommentToken = "%".$self->add_comment_symbol; + } else { + $self->logger("Adding a linebreak at the beginning of ${$child}{begin} (see $BeginStringLogFile)") if $is_t_switch_active; + } + + # the trailing comment/linebreak magic + ${$child}{begin} = "$trailingCommentToken\n".${$child}{begin}; + $child->add_surrounding_indentation_to_begin_statement; + + # remove surrounding indentation ahead of % + ${$child}{begin} =~ s/^(\h*)%/%/ if(${$child}{BeginStartsOnOwnLine}==2); + } elsif (${$child}{BeginStartsOnOwnLine}==-1 and $IDFirstNonWhiteSpaceCharacter){ + # important to check we don't move the begin statement next to a blank-line-token + my $blankLineToken = $tokens{blanklines}; + if(${$self}{body} !~ m/$blankLineToken\R*\h*${$child}{id}/s){ + $self->logger("Removing linebreak before ${$child}{begin} (see $BeginStringLogFile in ${$child}{modifyLineBreaksYamlName} YAML)") if $is_t_switch_active; + ${$self}{body} =~ s/(\h*)(?:\R*|\h*)+${$child}{id}/$1${$child}{id}/s; + } else { + $self->logger("Not removing linebreak ahead of ${$child}{begin}, as blank-line-token present (see preserveBlankLines)") if $is_t_switch_active; + } + } + } + + $self->logger(Dumper(\%{$child}),'ttrace') if($is_tt_switch_active); + + # replace ids with body + ${$self}{body} =~ s/${$child}{id}/${$child}{begin}${$child}{body}${$child}{end}/; + + # log file info + $self->logger("Body (${$self}{name}) now looks like:",'heading') if $is_t_switch_active; + $self->logger(${$self}{body}) if($is_t_switch_active); + + # remove element from array: http://stackoverflow.com/questions/174292/what-is-the-best-way-to-delete-a-value-from-an-array-in-perl + splice(@{${$self}{children}}, $index, 1); + + # output to the log file + $self->logger("deleted child key ${$child}{name} (parent is: ${$self}{name})") if $is_t_switch_active; + + # restart the loop, as the size of the array has changed + last; + } else { + $self->logger("${$child}{id} not found") if($is_t_switch_active); + } + + # increment the loop counter + $index++; + } + } + + # logfile info + $self->logger("${$self}{name} has this many children:",'heading') if $is_t_switch_active; + $self->logger(scalar @{${$self}{children}}) if $is_t_switch_active; + $self->logger("Post-processed body (${$self}{name}):") if($is_t_switch_active); + $self->logger(${$self}{body}) if($is_t_switch_active); + +} + +sub add_surrounding_indentation_to_begin_statement{ + # almost all of the objects add surrounding indentation to the 'begin' statements, + # but some (e.g HEADING) have their own method + my $self = shift; + + my $surroundingIndentation = ${$self}{surroundingIndentation}; + ${$self}{begin} =~ s/^(\h*)?/$surroundingIndentation/mg; # add indentation + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Item.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Item.pm new file mode 100644 index 00000000000..d85f4e24c31 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Item.pm @@ -0,0 +1,149 @@ +package LatexIndent::Item; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_items construct_list_of_items/; +our $itemCounter; +our $listOfItems = q(); +our $itemRegExp; + +sub construct_list_of_items{ + my $self = shift; + + # put together a list of the items + while( my ($item,$lookForThisItem)= each %{$masterSettings{itemNames}}){ + $listOfItems .= ($listOfItems eq "")?"$item":"|$item" if($lookForThisItem); + } + + # detail items in the log + $self->logger("List of items: $listOfItems (see itemNames)",'heading'); + + $itemRegExp = qr/ + ( + \\($listOfItems) + \h* + (\R*)? + ) + ( + (?: # cluster-only (), don't capture + (?! + (?:\\(?:$listOfItems)) # cluster-only (), don't capture + ). # any character, but not \\$item + )* + ) + (\R)? + /sx; + + + return; +} + +sub find_items{ + # no point carrying on if the list of items is empty + return if($listOfItems eq ""); + + my $self = shift; + + return unless ${$masterSettings{indentAfterItems}}{${$self}{name}}; + + # otherwise loop through the item names + $self->logger("Searching for items (see itemNames) in ${$self}{name} (see indentAfterItems)") if $is_t_switch_active; + $self->logger(Dumper(\%{$masterSettings{itemNames}})) if $is_t_switch_active; + + while(${$self}{body} =~ m/$itemRegExp\h*($trailingCommentRegExp)?/){ + + # log file output + $self->logger("Item found: $2",'heading') if $is_t_switch_active; + + ${$self}{body} =~ s/ + $itemRegExp(\h*)($trailingCommentRegExp)? + / + # create a new Item object + my $itemObject = LatexIndent::Item->new(begin=>$1, + body=>$4, + end=>q(), + name=>$2, + linebreaksAtEnd=>{ + begin=>$3?1:0, + body=>$5?1:0, + }, + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"ItemStartsOnOwnLine", + # body statements + BodyStartsOnOwnLine=>"ItemFinishesWithLineBreak", + }, + modifyLineBreaksYamlName=>"items", + endImmediatelyFollowedByComment=>$5?0:($7?1:0), + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($itemObject); + ${@{${$self}{children}}[-1]}{replacementText}.($6?$6:q()).($7?$7:q()); + /xseg; + } +} + + +sub create_unique_id{ + my $self = shift; + + $itemCounter++; + + ${$self}{id} = "$tokens{item}$itemCounter"; + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # the item body could hoover up line breaks; we do an additional check + ${${$self}{linebreaksAtEnd}}{body}=1 if(${$self}{body} =~ m/\R+$/s ); + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for headings (part, chapter, section, setc) + $self->find_heading; + + # search for commands with arguments + $self->find_commands_or_key_equals_values_braces; + + # search for special begin/end + $self->find_special; + +} + +sub remove_line_breaks_begin{ + # the \item command can need a trailing white space if the line breaks have been removed after it and + # there is no white space + my $self = shift; + my $BodyStringLogFile = ${$self}{aliases}{BodyStartsOnOwnLine}||"BodyStartsOnOwnLine"; + $self->logger("Removing linebreak at the end of begin (see $BodyStringLogFile)"); + ${$self}{begin} =~ s/\R*$//sx; + ${$self}{begin} .= " " unless(${$self}{begin} =~ m/\h$/s or ${$self}{body} =~ m/^\h/s or ${$self}{body} =~ m/^\R/s ); + ${$self}{linebreaksAtEnd}{begin} = 0; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/KeyEqualsValuesBraces.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/KeyEqualsValuesBraces.pm new file mode 100755 index 00000000000..41fcb981a15 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/KeyEqualsValuesBraces.pm @@ -0,0 +1,122 @@ +package LatexIndent::KeyEqualsValuesBraces; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_m_switch_active/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Command"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/construct_key_equals_values_regexp $key_equals_values_bracesRegExp $key_equals_values_bracesRegExpTrailingComment/; +our $key_equals_values_braces_Counter; +our $key_equals_values_bracesRegExp; +our $key_equals_values_bracesRegExpTrailingComment; + +sub construct_key_equals_values_regexp{ + my $self = shift; + + # grab the arguments regexp + my $optAndMandRegExp = $self->get_arguments_regexp; + + # blank line token + my $blankLineToken = $tokens{blanklines}; + + # store the regular expresssion for matching and replacing + $key_equals_values_bracesRegExp = qr/ + ( + (?: + (?:(?<!\\)\{) + | + , + | + (?:(?<!\\)\[) + ) + (?:\h|\R|$blankLineToken|$trailingCommentRegExp)* + ) # $1 pre-key bit: could be { OR , OR [ + (\\)? # $2 possible backslash + ( + [a-zA-Z@\*0-9_\/.\h\{\}:\#-]+? # lowercase|uppercase letters, @, *, numbers, forward slash, dots + ) # $3 name + ( + (?:\h|\R|$blankLineToken|$trailingCommentRegExp)* + =\h* + (?:\d*\:?) + ) # $4 = symbol + (\R*)? # $5 linebreak after = + ($optAndMandRegExp) # $6 opt|mand arguments + (\R)? # $9 linebreak at end + /sx; + + $key_equals_values_bracesRegExpTrailingComment = qr/$key_equals_values_bracesRegExp(\h*)((?:$trailingCommentRegExp\h*)*)?/; +} + +sub indent_begin{ + my $self = shift; + + # blank line token + my $blankLineToken = $tokens{blanklines}; + + if(${$self}{begin} =~ /\R=/s or ${$self}{begin} =~ /$blankLineToken\h*=/s ){ + $self->logger("= found on own line in ${$self}{name}, adding indentation") if $is_t_switch_active; + ${$self}{begin} =~ s/=/${$self}{indentation}=/s; + } +} + +sub check_linebreaks_before_equals{ + # check if -m switch is active + return unless $is_m_switch_active; + + my $self = shift; + + # linebreaks *infront* of = symbol + if(${$self}{begin} =~ /\R\h*=/s){ + if(defined ${$self}{EqualsStartsOnOwnLine} and ${$self}{EqualsStartsOnOwnLine}==-1){ + $self->logger("Removing linebreak before = symbol in ${$self}{name} (see EqualsStartsOnOwnLine)") if $is_t_switch_active; + ${$self}{begin} =~ s/(\R|\h)*=/=/s; + } + } else { + if(defined ${$self}{EqualsStartsOnOwnLine} and ${$self}{EqualsStartsOnOwnLine}==1){ + $self->logger("Adding a linebreak before = symbol for ${$self}{name} (see EqualsStartsOnOwnLine)") if $is_t_switch_active; + ${$self}{begin} =~ s/=/\n=/s; + } elsif(defined ${$self}{EqualsStartsOnOwnLine} and ${$self}{EqualsStartsOnOwnLine}==2){ + $self->logger("Adding a % linebreak immediately before = symbol for ${$self}{name} (see EqualsStartsOnOwnLine)") if $is_t_switch_active; + ${$self}{begin} =~ s/\h*=/%\n=/s; + } + } + return; +} + +sub create_unique_id{ + my $self = shift; + + $key_equals_values_braces_Counter++; + ${$self}{id} = "$tokens{key_equals_values_braces}$key_equals_values_braces_Counter"; + return; +} + +sub get_replacement_text{ + my $self = shift; + + # the replacement text for a key = {value} needes to accomodate the leading [ OR { OR % OR , OR any combination thereof + $self->logger("Custom replacement text routine for ${$self}{name}") if $is_t_switch_active; + ${$self}{replacementText} = ${$self}{beginningbit}.${$self}{id}; + delete ${$self}{beginningbit}; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/LogFile.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/LogFile.pm new file mode 100644 index 00000000000..89cffa58a81 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/LogFile.pm @@ -0,0 +1,166 @@ +package LatexIndent::LogFile; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/%switches/; +use FindBin; +use File::Basename; # to get the filename and directory path +use Exporter qw/import/; +our @EXPORT_OK = qw/logger output_logfile processSwitches/; +our @logFileNotes; + +# log file methods +# log file methods +# log file methods +# reference: http://stackoverflow.com/questions/6736998/help-calling-a-sub-routine-from-a-perl-module-and-printing-to-logfile + +sub logger{ + shift; + my $line = shift; + my $infoLevel = shift; + push(@logFileNotes,{line=>$line,level=>$infoLevel?$infoLevel:'default'}); + return +} + +sub processSwitches{ + my $self = shift; + + # details of the script to log file + $self->logger("$FindBin::Script version 3.0, a script to indent .tex files",'heading'); + $self->logger("$FindBin::Script lives here: $FindBin::RealBin/"); + + # time the script is used + my $time = localtime(); + $self->logger("$time"); + + if(scalar(@ARGV) < 1 or $switches{showhelp}) { + print <<ENDQUOTE +latexindent.pl version 3.0 +usage: latexindent.pl [options] [file][.tex|.sty|.cls|.bib|...] + -h, --help + help (see the documentation for detailed instructions and examples) + -o, --outputfile + output to another file; sample usage: + latexindent.pl -o outputfile.tex myfile.tex + latexindent.pl -o=outputfile.tex myfile.tex + -w, --overwrite + overwrite the current file; a backup will be made, but still be careful + -s, --silent + silent mode: no output will be given to the terminal + -t, --trace + tracing mode: verbose information given to the log file + -l, --local[=myyaml.yaml] + use localSettings.yaml (assuming it exists in the directory of your file); + alternatively, use myyaml.yaml, if it exists; sample usage: + latexindent.pl -l some.yaml myfile.tex + latexindent.pl -l=another.yaml myfile.tex + latexindent.pl -l=some.yaml,another.yaml myfile.tex + -d, --onlydefault + ONLY use defaultSettings.yaml, ignore ALL (yaml) user files + -g, --logfile + used to specify the name of logfile (default is indent.log) + -c, --cruft=<cruft directory> + used to specify the location of backup files and indent.log + -m, --modifylinebreaks + modify linebreaks before, during, and at the end of code blocks; + trailing comments can also be added using this feature +ENDQUOTE + ; + exit(2); +} + + # log the switches from the user + $self->logger('Processing switches','heading'); + + # check on the trace mode switch (should be turned on if ttrace mode active) + $switches{trace} = $switches{ttrace} ? 1 : $switches{trace}; + + # output details of switches + $self->logger('-t|--trace: Trace mode active (you have used either -t or --trace)') if($switches{trace} and !$switches{ttrace}); + $self->logger('-tt|--ttrace: TTrace mode active (you have used either -tt or --ttrace)') if($switches{tracingModeVeryDetailed}); + $self->logger('-s|--silent: Silent mode active (you have used either -s or --silent)') if($switches{silentMode}); + $self->logger('-d|--onlydefault: Only defaultSettings.yaml will be used (you have used either -d or --onlydefault)') if($switches{onlyDefault}); + $self->logger("-w|--overwrite: Overwrite mode active, will make a back up of ${$self}{fileName} first") if($switches{overwrite}); + $self->logger("-l|--localSettings: Read localSettings YAML file") if($switches{readLocalSettings}); + $self->logger("-o|--outputfile: output to file") if($switches{outputToFile}); + $self->logger("-m|--modifylinebreaks: modify line breaks") if($switches{modifyLineBreaks}); + $self->logger("-g|--logfile: logfile name") if($switches{logFileName}); + $self->logger("-c|--cruft: cruft directory") if($switches{cruftDirectory}); + + # check if overwrite and outputfile are active similtaneously + if($switches{overwrite} and $switches{outputToFile}){ + $self->logger("Options check",'heading'); + $self->logger("You have called latexindent.pl with both -o and -w"); + $self->logger("-o (output to file) will take priority, and -w (over write) will be ignored"); + $switches{overwrite}=0; + } + + # cruft directory + ${$self}{cruftDirectory} = $switches{cruftDirectory}||(dirname ${$self}{fileName}); + die "Could not find directory ${$self}{cruftDirectory}\nExiting, no indentation done." if(!(-d ${$self}{cruftDirectory})); + my $logfileName = $switches{logFileName}||"indent.log"; + $self->logger("Directory for backup files and $logfileName: ${$self}{cruftDirectory}",'heading'); + + # output location of modules + if($FindBin::Script eq 'latexindent.pl' or ($FindBin::Script eq 'latexindent.exe' and $switches{trace} )) { + my @listOfModules = ('FindBin', 'YAML::Tiny', 'File::Copy', 'File::Basename', 'Getopt::Long','File::HomeDir'); + $self->logger("Perl modules are being loaded from the following directories:",'heading'); + foreach my $moduleName (@listOfModules) { + (my $file = $moduleName) =~ s|::|/|g; + require $file . '.pm'; + $self->logger($INC{$file .'.pm'}); + } + $self->logger("Latex Indent perl modules are being loaded from, for example:",'heading'); + (my $file = 'LatexIndent::Document') =~ s|::|/|g; + require $file . '.pm'; + $self->logger($INC{$file .'.pm'}); + } + return; +} + +sub output_logfile{ + my $self = shift; + my $logfile; + my $logfileName = $switches{logFileName}||"indent.log"; + + open($logfile,">","${$self}{cruftDirectory}/$logfileName") or die "Can't open $logfileName"; + + # put the final line in the logfile + $self->logger("${$masterSettings{logFilePreferences}}{endLogFileWith}",'heading'); + + # github info line + $self->logger("Please direct all communication/issues to: ",'heading') if ${$masterSettings{logFilePreferences}}{showGitHubInfoFooter}; + $self->logger("https://github.com/cmhughes/latexindent.pl") if ${$masterSettings{logFilePreferences}}{showGitHubInfoFooter}; + + # output the logfile + foreach my $line (@logFileNotes){ + if(${$line}{level} eq 'heading'){ + print $logfile ${$line}{line},"\n"; + } elsif(${$line}{level} eq 'default') { + # add tabs to the beginning of lines + # for default logfile lines + ${$line}{line} =~ s/^/\t/mg; + print $logfile ${$line}{line},"\n"; + } + } + + close($logfile); +} + + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/MandatoryArgument.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/MandatoryArgument.pm new file mode 100644 index 00000000000..172e002eda4 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/MandatoryArgument.pm @@ -0,0 +1,130 @@ +package LatexIndent::MandatoryArgument; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_mandatory_arguments get_mand_arg_reg_exp/; +our $mandatoryArgumentCounter; + +sub find_mandatory_arguments{ + my $self = shift; + + my $mandArgRegExp = $self->get_mand_arg_reg_exp; + + # pick out the mandatory arguments + while(${$self}{body} =~ m/$mandArgRegExp\h*($trailingCommentRegExp)*(.*)/s){ + # log file output + $self->logger("Mandatory argument found, body in ${$self}{name}",'heading') if $is_t_switch_active; + $self->logger("(last argument)") if($9 eq '' and $is_t_switch_active); + + ${$self}{body} =~ s/ + $mandArgRegExp(\h*)($trailingCommentRegExp)*(.*) + / + # create a new Mandatory Argument object + my $mandatoryArg = LatexIndent::MandatoryArgument->new(begin=>$1, + name=>${$self}{name}.":mandatoryArgument", + nameForIndentationSettings=>${$self}{parent}, + parent=>${$self}{parent}, + body=>$3.($4?$4:q()), + end=>$5, + linebreaksAtEnd=>{ + begin=>$2?1:0, + body=>$4?1:0, + end=>$7?1:0, + }, + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"LCuBStartsOnOwnLine", + # body statements + BodyStartsOnOwnLine=>"MandArgBodyStartsOnOwnLine", + # end statements + EndStartsOnOwnLine=>"RCuBStartsOnOwnLine", + # after end statements + EndFinishesWithLineBreak=>"RCuBFinishesWithLineBreak", + }, + horizontalTrailingSpace=>$6?$6:q(), + modifyLineBreaksYamlName=>"mandatoryArguments", + # the last argument (determined by $10 eq '') needs information from the argument container object + endImmediatelyFollowedByComment=>($10 eq '')?${$self}{endImmediatelyFollowedByComment}:($9?1:0), + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($mandatoryArg); + ${@{${$self}{children}}[-1]}{replacementText}.($8?$8:q()).($9?$9:q()).($10?$10:q()); + /xseg; + $self->wrap_up_tasks; + } + } + +sub create_unique_id{ + my $self = shift; + + $mandatoryArgumentCounter++; + ${$self}{id} = "$tokens{mandatoryArgument}$mandatoryArgumentCounter"; + return; +} + +sub get_mand_arg_reg_exp{ + + my $mandArgRegExp = qr/ + (?<!\\) # not immediately pre-ceeded by \ + ( + \{ + \h* + (\R*) # linebreaks after { into $2 + ) # { captured into $1 + ( + (?: + (?! + (?:(?<!\\)\{) + ). + )*? # not including {, but \{ ok + ) # body into $3 + (\R*) # linebreaks after body into $4 + (?<!\\) # not immediately pre-ceeded by \ + ( + \} # {mandatory arguments} + ) # } into $5 + (\h*) + (\R)? # linebreaks after } into $6 + /sx; + + return $mandArgRegExp; +} + +sub get_object_attribute_for_indentation_settings{ + my $self = shift; + + return ${$self}{modifyLineBreaksYamlName}; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for special begin/end + $self->find_special; + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/ModifyLineBreaks.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/ModifyLineBreaks.pm new file mode 100644 index 00000000000..bcfb2336264 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/ModifyLineBreaks.pm @@ -0,0 +1,174 @@ +package LatexIndent::ModifyLineBreaks; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use Data::Dumper; +use Exporter qw/import/; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +our @EXPORT_OK = qw/modify_line_breaks_body modify_line_breaks_end adjust_line_breaks_end_parent remove_line_breaks_begin/; +our @allObjects; + +sub modify_line_breaks_body{ + my $self = shift; + + # add a line break after \begin{statement} if appropriate + if(defined ${$self}{BodyStartsOnOwnLine}){ + my $BodyStringLogFile = ${$self}{aliases}{BodyStartsOnOwnLine}||"BodyStartsOnOwnLine"; + if(${$self}{BodyStartsOnOwnLine}>=1 and !${$self}{linebreaksAtEnd}{begin}){ + if(${$self}{BodyStartsOnOwnLine}==1){ + # modify the begin statement + $self->logger("Adding a linebreak at the end of begin, ${$self}{begin} (see $BodyStringLogFile)") if $is_t_switch_active; + ${$self}{begin} .= "\n"; + ${$self}{linebreaksAtEnd}{begin} = 1; + $self->logger("Removing leading space from body of ${$self}{name} (see $BodyStringLogFile)") if $is_t_switch_active; + ${$self}{body} =~ s/^\h*//; + } elsif(${$self}{BodyStartsOnOwnLine}==2){ + # by default, assume that no trailing comment token is needed + my $trailingCommentToken = q(); + if(${$self}{body} !~ m/^\h*$trailingCommentRegExp/s){ + # modify the begin statement + $self->logger("Adding a % at the end of begin, ${$self}{begin}, followed by a linebreak ($BodyStringLogFile == 2)") if $is_t_switch_active; + $trailingCommentToken = "%".$self->add_comment_symbol; + ${$self}{begin} =~ s/\h*$//; + ${$self}{begin} .= "$trailingCommentToken\n"; + ${$self}{linebreaksAtEnd}{begin} = 1; + $self->logger("Removing leading space from body of ${$self}{name} (see $BodyStringLogFile)") if $is_t_switch_active; + ${$self}{body} =~ s/^\h*//; + } else { + $self->logger("Even though $BodyStringLogFile == 2, ${$self}{begin} already finishes with a %, so not adding another.") if $is_t_switch_active; + } + } + } elsif (${$self}{BodyStartsOnOwnLine}==-1 and ${$self}{linebreaksAtEnd}{begin}){ + # remove line break *after* begin, if appropriate + $self->remove_line_breaks_begin; + } + } + } + +sub remove_line_breaks_begin{ + my $self = shift; + my $BodyStringLogFile = ${$self}{aliases}{BodyStartsOnOwnLine}||"BodyStartsOnOwnLine"; + $self->logger("Removing linebreak at the end of begin (see $BodyStringLogFile)") if $is_t_switch_active; + ${$self}{begin} =~ s/\R*$//sx; + ${$self}{linebreaksAtEnd}{begin} = 0; +} + +sub modify_line_breaks_end{ + my $self = shift; + + # possibly modify line break *before* \end{statement} + if(defined ${$self}{EndStartsOnOwnLine}){ + my $EndStringLogFile = ${$self}{aliases}{EndStartsOnOwnLine}||"EndStartsOnOwnLine"; + if(${$self}{EndStartsOnOwnLine}>=1 and !${$self}{linebreaksAtEnd}{body}){ + # add a line break after body, if appropriate + $self->logger("Adding a linebreak at the end of body (see $EndStringLogFile)") if $is_t_switch_active; + + # by default, assume that no trailing comment token is needed + my $trailingCommentToken = q(); + if(${$self}{EndStartsOnOwnLine}==2){ + $self->logger("Adding a % immediately after body of ${$self}{name} ($EndStringLogFile==2)") if $is_t_switch_active; + $trailingCommentToken = "%".$self->add_comment_symbol; + ${$self}{body} =~ s/\h*$//s; + } + + # modified end statement + if(${$self}{body} =~ m/^\h*$/s and ${$self}{BodyStartsOnOwnLine} >=1 ){ + ${$self}{linebreaksAtEnd}{body} = 0; + } else { + ${$self}{body} .= "$trailingCommentToken\n"; + ${$self}{linebreaksAtEnd}{body} = 1; + } + } elsif (${$self}{EndStartsOnOwnLine}==-1 and ${$self}{linebreaksAtEnd}{body}){ + # remove line break *after* body, if appropriate + + # check to see that body does *not* finish with blank-line-token, + # if so, then don't remove that final line break + if(${$self}{body} !~ m/$tokens{blanklines}$/s){ + $self->logger("Removing linebreak at the end of body (see $EndStringLogFile)") if $is_t_switch_active; + ${$self}{body} =~ s/\R*$//sx; + ${$self}{linebreaksAtEnd}{body} = 0; + } else { + $self->logger("Blank line token found at end of body (${$self}{name}), see preserveBlankLines, not removing line break before ${$self}{end}") if $is_t_switch_active; + } + } + } + + # possibly modify line break *after* \end{statement} + if(defined ${$self}{EndFinishesWithLineBreak} + and ${$self}{EndFinishesWithLineBreak}>=1 + and !${$self}{linebreaksAtEnd}{end}){ + my $EndStringLogFile = ${$self}{aliases}{EndFinishesWithLineBreak}||"EndFinishesWithLineBreak"; + if(${$self}{EndFinishesWithLineBreak}==1){ + $self->logger("Adding a linebreak at the end of ${$self}{end} (see $EndStringLogFile)") if $is_t_switch_active; + ${$self}{linebreaksAtEnd}{end} = 1; + + # modified end statement + ${$self}{replacementText} .= "\n"; + } elsif(${$self}{EndFinishesWithLineBreak}==2){ + if(${$self}{endImmediatelyFollowedByComment}){ + # no need to add a % if one already exists + $self->logger("Even though $EndStringLogFile == 2, ${$self}{end} is immediately followed by a %, so not adding another; not adding line break.") if $is_t_switch_active; + } else { + # otherwise, create a trailing comment, and tack it on + $self->logger("Adding a % immediately after, ${$self}{end} ($EndStringLogFile==2)") if $is_t_switch_active; + my $trailingCommentToken = "%".$self->add_comment_symbol; + ${$self}{end} =~ s/\h*$//s; + ${$self}{replacementText} .= "$trailingCommentToken\n"; + ${$self}{linebreaksAtEnd}{end} = 1; + } + } + } + +} + +sub adjust_line_breaks_end_parent{ + # when a parent object contains a child object, the line break + # at the end of the parent object can become messy + return unless $is_m_switch_active; + + my $self = shift; + + # most recent child object + my $child = @{${$self}{children}}[-1]; + + # adjust parent linebreaks information + if(${$child}{linebreaksAtEnd}{end} and ${$self}{body} =~ m/${$child}{replacementText}\h*\R*$/s and !${$self}{linebreaksAtEnd}{body}){ + $self->logger("ID: ${$child}{id}") if($is_t_switch_active); + $self->logger("${$child}{begin}...${$child}{end} is found at the END of body of parent, ${$self}{name}, avoiding a double line break:") if($is_t_switch_active); + $self->logger("adjusting ${$self}{name} linebreaksAtEnd{body} to be 1") if($is_t_switch_active); + ${$self}{linebreaksAtEnd}{body}=1; + } + + # the modify line switch can adjust line breaks, so we need another check, + # see for example, test-cases/environments/environments-remove-line-breaks-trailing-comments.tex + if(defined ${$child}{linebreaksAtEnd}{body} + and !${$child}{linebreaksAtEnd}{body} + and ${$child}{body} =~ m/\R(?:$trailingCommentRegExp\h*)?$/s ){ + # log file information + $self->logger("Undisclosed line break at the end of body of ${$child}{name}: '${$child}{end}'") if($is_t_switch_active); + $self->logger("Adding a linebreak at the end of body for ${$child}{id}") if($is_t_switch_active); + + # make the adjustments + ${$child}{body} .= "\n"; + ${$child}{linebreaksAtEnd}{body}=1; + } + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/NamedGroupingBracesBrackets.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/NamedGroupingBracesBrackets.pm new file mode 100755 index 00000000000..ea2c5323cf3 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/NamedGroupingBracesBrackets.pm @@ -0,0 +1,71 @@ +package LatexIndent::NamedGroupingBracesBrackets; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Exporter qw/import/; +our @ISA = "LatexIndent::Command"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/construct_grouping_braces_brackets_regexp $grouping_braces_regexp $grouping_braces_regexpTrailingComment/; +our $groupingBracesCounter; +our $grouping_braces_regexp; +our $grouping_braces_regexpTrailingComment; + +sub construct_grouping_braces_brackets_regexp{ + my $self = shift; + + # grab the arguments regexp + my $optAndMandRegExp = $self->get_arguments_regexp; + + # store the regular expresssion for matching and replacing + $grouping_braces_regexp = qr/ + ( + \h|\R|\{|\[|\$|\) + ) + ( + [0-9a-zA-Z@\*><]+? # lowercase|uppercase letters, @, *, numbers, forward slash, dots + ) # $2 name + (\h*) # $3 h-space + (\R*) # $4 linebreaks + ($optAndMandRegExp) # $5 mand|opt arguments (at least one) + (\R)? # $8 linebreak + /sx; + + # something {value} grouping braces with trailing comment + $grouping_braces_regexpTrailingComment = qr/$grouping_braces_regexp\h*((?:$trailingCommentRegExp\h*)*)?/; + +} + +sub create_unique_id{ + my $self = shift; + + $groupingBracesCounter++; + ${$self}{id} = "$tokens{groupingBraces}$groupingBracesCounter"; + return; +} + +sub get_replacement_text{ + my $self = shift; + + # the replacement text for a key = {value} needes to accomodate the leading [ OR { OR % OR , OR any combination thereof + $self->logger("Custom replacement text routine for ${$self}{name}") if $is_t_switch_active; + ${$self}{replacementText} = ${$self}{beginningbit}.${$self}{id}; + delete ${$self}{beginningbit}; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/OptionalArgument.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/OptionalArgument.pm new file mode 100644 index 00000000000..b949e9b7e92 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/OptionalArgument.pm @@ -0,0 +1,115 @@ +package LatexIndent::OptionalArgument; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_optional_arguments/; +our $optionalArgumentCounter; +our $optArgRegExp = qr/ + (?<!\\) # not immediately pre-ceeded by \ + ( + \[ + \h* + (\R*) + ) + (.*?) + (\R*) + (?<!\\) # not immediately pre-ceeded by \ + ( + \] # [optional arguments] + ) + (\h*) + (\R)? + /sx; + +sub find_optional_arguments{ + my $self = shift; + + # pick out the optional arguments + while(${$self}{body} =~ m/$optArgRegExp\h*($trailingCommentRegExp)*(.*)/s){ + # log file output + $self->logger("Optional argument found, body in ${$self}{name}",'heading') if $is_t_switch_active; + $self->logger("(last argument)") if($9 eq '' and $is_t_switch_active); + + ${$self}{body} =~ s/ + $optArgRegExp(\h*)($trailingCommentRegExp)*(.*) + / + # create a new Optional Argument object + my $optionalArg = LatexIndent::OptionalArgument->new(begin=>$1, + name=>${$self}{name}.":optionalArgument", + nameForIndentationSettings=>${$self}{parent}, + parent=>${$self}{parent}, + body=>$3.($4?$4:q()), + end=>$5, + linebreaksAtEnd=>{ + begin=>$2?1:0, + body=>$4?1:0, + end=>$7?1:0, + }, + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"LSqBStartsOnOwnLine", + # body statements + BodyStartsOnOwnLine=>"OptArgBodyStartsOnOwnLine", + # end statements + EndStartsOnOwnLine=>"RSqBStartsOnOwnLine", + # after end statements + EndFinishesWithLineBreak=>"RSqBFinishesWithLineBreak", + }, + modifyLineBreaksYamlName=>"optionalArguments", + # the last argument (determined by $10 eq '') needs information from the argument container object + endImmediatelyFollowedByComment=>($10 eq '')?${$self}{endImmediatelyFollowedByComment}:($9?1:0), + horizontalTrailingSpace=>$6?$6:q(), + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($optionalArg); + ${@{${$self}{children}}[-1]}{replacementText}.($8?$8:q()).($9?$9:q()).($10?$10:q()); + /xseg; + } + } + +sub get_object_attribute_for_indentation_settings{ + my $self = shift; + + return ${$self}{modifyLineBreaksYamlName}; +} + +sub create_unique_id{ + my $self = shift; + + $optionalArgumentCounter++; + ${$self}{id} = "$tokens{optionalArgument}$optionalArgumentCounter"; + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for special begin/end + $self->find_special; + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Preamble.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Preamble.pm new file mode 100644 index 00000000000..a3a62dd3f56 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Preamble.pm @@ -0,0 +1,64 @@ +package LatexIndent::Preamble; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our $preambleCounter; + +sub create_unique_id{ + my $self = shift; + + $preambleCounter++; + ${$self}{id} = "$tokens{preamble}$preambleCounter$tokens{endOfToken}"; + return; +} + +sub get_replacement_text{ + my $self = shift; + + # the replacement text for preamble needs to put the \\begin{document} back in + $self->logger("Custom replacement text routine for preamble ${$self}{name}"); + ${$self}{replacementText} = ${$self}{id}.${$self}{afterbit}; + delete ${$self}{afterbit}; +} + +sub indent{ + # preamble doesn't receive any additional indentation + return; +} + +sub tasks_particular_to_each_object{ + my $self = shift; + + # search for environments + $self->find_environments; + + # search for ifElseFi blocks + $self->find_ifelsefi; + + # search for commands with arguments + $self->find_commands_or_key_equals_values_braces if(!$masterSettings{preambleCommandsBeforeEnvironments}); + + # search for special begin/end + $self->find_special; + + +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/RoundBrackets.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/RoundBrackets.pm new file mode 100644 index 00000000000..f320d007fa2 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/RoundBrackets.pm @@ -0,0 +1,100 @@ +package LatexIndent::RoundBrackets; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_round_brackets/; +our $roundBracketCounter; +our $roundBracketRegExp = qr/ + (?<!\\) + ( + \( + \h* + (\R*) + ) + (.*?) + (\R*) + (?<!\\) # not immediately pre-ceeded by \ + ( + \) # [optional arguments] + ) + (\h*) + (\R)? + /sx; + +sub indent{ + return; +} + +sub find_round_brackets{ + my $self = shift; + + # pick out the optional arguments + while(${$self}{body} =~ m/$roundBracketRegExp\h*($trailingCommentRegExp)*(.*)/s){ + # log file output + + ${$self}{body} =~ s/ + $roundBracketRegExp(\h*)($trailingCommentRegExp)*(.*) + / + # create a new Optional Argument object + my $roundBracket = LatexIndent::RoundBrackets->new(begin=>$1, + name=>${$self}{name}.":roundBracket", + nameForIndentationSettings=>${$self}{parent}, + parent=>${$self}{parent}, + body=>$3.($4?$4:q()), + end=>$5, + linebreaksAtEnd=>{ + begin=>$2?1:0, + body=>$4?1:0, + end=>$7?1:0, + }, + modifyLineBreaksYamlName=>"roundBracket", + # the last argument (determined by $10 eq '') needs information from the argument container object + endImmediatelyFollowedByComment=>($10 eq '')?${$self}{endImmediatelyFollowedByComment}:($9?1:0), + horizontalTrailingSpace=>$6?$6:q(), + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($roundBracket); + ${@{${$self}{children}}[-1]}{replacementText}.($8?$8:q()).($9?$9:q()).($10?$10:q()); + /xseg; + } + } + +sub get_object_attribute_for_indentation_settings{ + my $self = shift; + + return ${$self}{modifyLineBreaksYamlName}; +} + +sub create_unique_id{ + my $self = shift; + + $roundBracketCounter++; + ${$self}{id} = "$tokens{roundBracket}$roundBracketCounter"; + return; +} + +sub tasks_particular_to_each_object{ + return; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Special.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Special.pm new file mode 100644 index 00000000000..ba7b905b492 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Special.pm @@ -0,0 +1,184 @@ +package LatexIndent::Special; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/find_special construct_special_begin/; +our $specialCounter; +our $specialBegins = q(); +our $specialAllMatchesRegExp = q(); +our %individualSpecialRegExps; + +sub construct_special_begin{ + my $self = shift; + + # put together a list of the begin terms in special + while( my ($specialName,$BeginEnd)= each %{$masterSettings{specialBeginEnd}}){ + # only append the regexps if lookForThis is 1 + $specialBegins .= ($specialBegins eq ""?q():"|").${$BeginEnd}{begin} if(${$BeginEnd}{lookForThis}); + } + + # put together a list of the begin terms in special + while( my ($specialName,$BeginEnd)= each %{$masterSettings{specialBeginEnd}}){ + + # only append the regexps if lookForThis is 1 + if(${$BeginEnd}{lookForThis}){ + # the beginning parts + $specialBegins .= ($specialBegins eq ""?q():"|").${$BeginEnd}{begin}; + + # the overall regexp + $specialAllMatchesRegExp .= ($specialAllMatchesRegExp eq ""?q():"|") + .qr/ + ${$BeginEnd}{begin} + (?: # cluster-only (), don't capture + (?! + (?:$specialBegins) # cluster-only (), don't capture + ). # any character, but not anything in $specialBegins + )*? + ${$BeginEnd}{end} + /sx; + + # store the individual special regexp + $individualSpecialRegExps{$specialName} = qr/ + ( + ${$BeginEnd}{begin} + \h* + (\R*)? + ) + ( + (?: # cluster-only (), don't capture + (?! + (?:$specialBegins) # cluster-only (), don't capture + ). # any character, but not anything in $specialBegins + )*? + (\R*)? + ) + ( + ${$BeginEnd}{end} + ) + (\h*) + (\R)? + /sx + + } else { + $self->logger("The special regexps won't include anything from $specialName (see lookForThis)",'heading') if $is_t_switch_active ; + } + } + + # move $$ to the beginning + if($specialBegins =~ m/\|\\\$\\\$/){ + $specialBegins =~ s/\|(\\\$\\\$)//; + $specialBegins = $1."|".$specialBegins; + } + + # info to the log file + $self->logger("The special beginnings regexp is: $specialBegins (see specialBeginEnd)",'heading') if $is_t_switch_active; + + # overall special regexp + $self->logger("The overall special regexp is: $specialAllMatchesRegExp(see specialBeginEnd)",'heading') if $is_t_switch_active; + + } + +sub find_special{ + my $self = shift; + + # no point carrying on if the list of specials is empty + return if($specialBegins eq ""); + + # otherwise loop through the special begin/end + $self->logger("Searching for special begin/end (see specialBeginEnd)") if $is_t_switch_active ; + $self->logger(Dumper(\%{$masterSettings{specialBeginEnd}})) if $is_tt_switch_active; + + # keep looping as long as there is a special match of some kind + while(${$self}{body} =~ m/$specialAllMatchesRegExp/sx){ + + # loop through each special match + while( my ($specialName,$BeginEnd)= each %{$masterSettings{specialBeginEnd}}){ + + # log file + if(${$BeginEnd}{lookForThis}){ + $self->logger("Looking for $specialName",'heading') if $is_t_switch_active ; + } else { + $self->logger("Not looking for $specialName (see lookForThis)",'heading') if $is_t_switch_active ; + next; + } + + # the regexp + my $specialRegExp = $individualSpecialRegExps{$specialName}; + + while(${$self}{body} =~ m/$specialRegExp(\h*)($trailingCommentRegExp)?/){ + + # global substitution + ${$self}{body} =~ s/ + $specialRegExp(\h*)($trailingCommentRegExp)? + / + # log file output + $self->logger("special found: $specialName",'heading') if $is_t_switch_active; + + # create a new special object + my $specialObject = LatexIndent::Special->new(begin=>$1, + body=>$3, + end=>$5, + name=>$specialName, + linebreaksAtEnd=>{ + begin=>$2?1:0, + body=>$4?1:0, + end=>$7?1:0, + }, + aliases=>{ + # begin statements + BeginStartsOnOwnLine=>"SpecialBeginStartsOnOwnLine", + # body statements + BodyStartsOnOwnLine=>"SpecialBodyStartsOnOwnLine", + # end statements + EndStartsOnOwnLine=>"SpecialEndStartsOnOwnLine", + # after end statements + EndFinishesWithLineBreak=>"SpecialEndFinishesWithLineBreak", + }, + modifyLineBreaksYamlName=>"specialBeginEnd", + endImmediatelyFollowedByComment=>$7?0:($9?1:0), + horizontalTrailingSpace=>$6?$6:q(), + ); + + # the settings and storage of most objects has a lot in common + $self->get_settings_and_store_new_object($specialObject); + ${@{${$self}{children}}[-1]}{replacementText}.($8?$8:q()).($9?$9:q()); + /xseg; + + $self->wrap_up_tasks; + } + } + } +} + + +sub create_unique_id{ + my $self = shift; + + $specialCounter++; + + ${$self}{id} = "$tokens{special}$specialCounter"; + return; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Switches.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Switches.pm new file mode 100644 index 00000000000..a5b07936a6d --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Switches.pm @@ -0,0 +1,37 @@ +package LatexIndent::Switches; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use Exporter qw/import/; +our @EXPORT_OK = qw/%switches storeSwitches $is_m_switch_active $is_t_switch_active $is_tt_switch_active/; +our %switches; +our $is_m_switch_active; +our $is_t_switch_active; +our $is_tt_switch_active; + +sub storeSwitches{ + my $self = shift; + + # copy document switches into hash local to this module + %switches = %{${$self}{switches}}; + $is_m_switch_active = defined $switches{modifyLineBreaks}?$switches{modifyLineBreaks}: 0; + $is_t_switch_active = defined $switches{trace}?$switches{trace}: 0; + $is_tt_switch_active = defined $switches{ttrace}?$switches{ttrace}: 0; + $is_t_switch_active = $is_tt_switch_active ? $is_tt_switch_active : $is_t_switch_active; + delete ${$self}{switches}; + } +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Tokens.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Tokens.pm new file mode 100644 index 00000000000..8a9a100dc26 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Tokens.pm @@ -0,0 +1,68 @@ +package LatexIndent::Tokens; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use Exporter qw/import/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +our @EXPORT_OK = qw/token_check %tokens/; + +# each of the tokens begins the same way -- this is exploited during the hidden Children routine +my $beginningToken = "LTXIN-TK-"; +my $ifelsefiSpecial = "!-!"; + +# the %tokens hash is passed around many modules +our %tokens = ( + environment=>$beginningToken."ENVIRONMENT", + ifelsefiSpecial=>$ifelsefiSpecial, + ifelsefi=>$ifelsefiSpecial.$beginningToken."IFELSEFI", + item=>$beginningToken."ITEMS", + trailingComment=>"latexindenttrailingcomment", + blanklines=>$beginningToken."blank-line", + arguments=>$beginningToken."ARGUMENTS", + optionalArgument=>$beginningToken."OPTIONAL-ARGUMENT", + mandatoryArgument=>$beginningToken."MANDATORY-ARGUMENT", + roundBracket=>$beginningToken."ROUND-BRACKET", + verbatim=>$beginningToken."VERBATIM", + command=>$beginningToken."COMMAND", + key_equals_values_braces=>$beginningToken."KEY-VALUE-BRACES", + groupingBraces=>$beginningToken."GROUPING-BRACES", + unNamedgroupingBraces=>$beginningToken."UN-NAMED-GROUPING-BRACES", + special=>$beginningToken."SPECIAL", + heading=>$beginningToken."HEADING", + filecontents=>$beginningToken."FILECONTENTS", + preamble=>$beginningToken."preamble", + beginOfToken=>$beginningToken, + doubleBackSlash=>$beginningToken."DOUBLEBACKSLASH", + alignmentBlock=>$beginningToken."ALIGNMENTBLOCK", + endOfToken=>"-END", + ); + +sub token_check{ + my $self = shift; + + $self->logger("Token check",'heading') if $is_t_switch_active; + # we use tokens for trailing comments, environments, commands, etc, so check that they're not in the body + foreach( keys %tokens){ + while(${$self}{body} =~ m/$tokens{$_}/si){ + $self->logger("Found $tokens{$_} within body, updating replacement token to $tokens{$_}-LIN") if($is_t_switch_active); + $tokens{$_} .= "-LIN"; + } + } +} + + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/TrailingComments.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/TrailingComments.pm new file mode 100644 index 00000000000..f8987d271f1 --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/TrailingComments.pm @@ -0,0 +1,112 @@ +package LatexIndent::TrailingComments; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @EXPORT_OK = qw/remove_trailing_comments put_trailing_comments_back_in $trailingCommentRegExp add_comment_symbol construct_trailing_comment_regexp/; +our @trailingComments; +our $commentCounter = 0; +our $trailingCommentRegExp; + +sub construct_trailing_comment_regexp{ + $trailingCommentRegExp = qr/(?<!\\)%$tokens{trailingComment}\d+$tokens{endOfToken}/; +} + +sub add_comment_symbol{ + # add a trailing comment token after, for example, a square brace [ + # or a curly brace { when, for example, BeginStartsOnOwnLine == 2 + my $self = shift; + + # increment the comment counter + $commentCounter++; + + # store the comment -- without this, it won't get processed correctly at the end + push(@trailingComments,{id=>$tokens{trailingComment}.$commentCounter.$tokens{endOfToken},value=>q()}); + + # log file info + $self->logger("Updating trailing comment array",'heading')if $is_t_switch_active; + $self->logger(Dumper(\@trailingComments),'ttrace') if($is_tt_switch_active); + + # the returned value + return $tokens{trailingComment}.$commentCounter.$tokens{endOfToken}; +} + +sub remove_trailing_comments{ + my $self = shift; + $self->logger("Storing trailing comments",'heading')if $is_t_switch_active; + + # perform the substitution + ${$self}{body} =~ s/ + (?<!\\) # not preceeded by a \ + % # % + ( + \h*? # followed by possible horizontal space + .*? # and anything else + ) + $ # up to the end of a line + / + # increment comment counter and store comment + $commentCounter++; + push(@trailingComments,{id=>$tokens{trailingComment}.$commentCounter.$tokens{endOfToken},value=>$1}); + + # replace comment with dummy text + "%".$tokens{trailingComment}.$commentCounter.$tokens{endOfToken}; + /xsmeg; + if(@trailingComments){ + $self->logger("Trailing comments stored in:") if($is_t_switch_active); + $self->logger(Dumper(\@trailingComments)) if($is_t_switch_active); + } else { + $self->logger("No trailing comments found") if($is_t_switch_active); + } + return; +} + +sub put_trailing_comments_back_in{ + my $self = shift; + return unless( @trailingComments > 0 ); + + $self->logger("Returning trailing comments to body",'heading')if $is_t_switch_active; + + # loop through trailing comments in reverse so that, for example, + # latexindenttrailingcomment1 doesn't match the first + # part of latexindenttrailingcomment18, which would result in an 8 left over (bad) + while( my $comment = pop @trailingComments){ + my $trailingcommentID = ${$comment}{id}; + my $trailingcommentValue = ${$comment}{value}; + if(${$self}{body} =~ m/%$trailingcommentID + ( + (?! # not immediately preceeded by + (?<!\\) # \ + % # % + ).*? + ) # captured into $1 + (\h*)?$ + /mx and $1 ne ''){ + $self->logger("Comment not at end of line $trailingcommentID, moving it to end of line")if $is_t_switch_active; + ${$self}{body} =~ s/%$trailingcommentID(.*)$/$1%$trailingcommentValue/m; + } else { + ${$self}{body} =~ s/%$trailingcommentID/%$trailingcommentValue/; + } + $self->logger("replace %$trailingcommentID with %$trailingcommentValue") if($is_t_switch_active); + } + return; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/UnNamedGroupingBracesBrackets.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/UnNamedGroupingBracesBrackets.pm new file mode 100644 index 00000000000..268f5b5adfa --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/UnNamedGroupingBracesBrackets.pm @@ -0,0 +1,135 @@ +package LatexIndent::UnNamedGroupingBracesBrackets; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::TrailingComments qw/$trailingCommentRegExp/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Exporter qw/import/; +our @ISA = "LatexIndent::Command"; # class inheritance, Programming Perl, pg 321 +our @EXPORT_OK = qw/construct_unnamed_grouping_braces_brackets_regexp $un_named_grouping_braces_RegExp $un_named_grouping_braces_RegExp_trailing_comment/; +our $unNamedGroupingBracesCounter; +our $un_named_grouping_braces_RegExp; +our $un_named_grouping_braces_RegExp_trailing_comment; + +sub construct_unnamed_grouping_braces_brackets_regexp{ + my $self = shift; + + # grab the arguments regexp + my $optAndMandRegExp = $self->get_arguments_regexp; + + # blank line token + my $blankLineToken = $tokens{blanklines}; + + # for example #1 #2, etc + my $numberedArgRegExp = $self->get_numbered_arg_regexp; + + # store the regular expresssion for matching and replacing + $un_named_grouping_braces_RegExp = qr/ + # NOT + (?! + (?: + (?:(?<!\\)\]) + | + (?:(?<!\\)\}) + ) + (?:\h|\R|$blankLineToken|$trailingCommentRegExp|$numberedArgRegExp)* # 0 or more h-space, blanklines, trailing comments + ) + # END of NOT + ( + (?: + \{|\[|,|&|\)|\(|\$ # starting with { OR [ OR , OR & OR ) OR ( OR $ + ) + \h* + ) # $1 into beginning bit + (\R*) # $2 linebreaksAtEnd of begin + ($optAndMandRegExp) # $3 mand|opt arguments (at least one) stored into body + (\R)? # $6 linebreak + /sx; + + $un_named_grouping_braces_RegExp_trailing_comment = qr/$un_named_grouping_braces_RegExp(\h*)((?:$trailingCommentRegExp\h*)*)?/; +} + +sub create_unique_id{ + my $self = shift; + + $unNamedGroupingBracesCounter++; + ${$self}{id} = "$tokens{unNamedgroupingBraces}$unNamedGroupingBracesCounter"; + return; +} + +sub get_replacement_text{ + my $self = shift; + + # the replacement text for a key = {value} needes to accomodate the leading [ OR { OR % OR , OR any combination thereof + $self->logger("Custom replacement text routine for ${$self}{name}") if $is_t_switch_active; + + # the un-named object is a little special, as it doesn't have a name; as such, if there are blank lines before + # the braces/brackets, we have to insert them + # + # also, the argument reg-exp can pick up a leading comment (with line break), which needs to be put + # into the replacement text (see documentation/demonstrations/pstricks.tex and test-cases/unnamed-braces/unnamed.tex for example) + ${$self}{body} =~ s/(.*?)(\{|\[)/$2/s; + ${$self}{replacementText} = ${$self}{beginningbit}.($1 ne ''?$1:q()).${$self}{id}; + + # but now turn off the switch for linebreaksAtEnd{begin}, otherwise the first brace gets too much indentation + # (see, for example, test-cases/namedGroupingBracesBrackets/special-characters-minimal.tex) + ${${$self}{linebreaksAtEnd}}{begin} = 0; + $self->logger("Beginning bit is: ${$self}{beginningbit}") if($is_t_switch_active); + delete ${$self}{beginningbit}; +} + +sub check_for_blank_lines_at_beginning{ + # some examples can have blank line tokens at the beginning of the body, + # which can confuse the routine below + # See, for example, + # test-cases/namedGroupingBracesBrackets/special-characters-minimal-blank-lines-m-switch.tex + # compared to + # test-cases/namedGroupingBracesBrackets/special-characters-minimal-blank-lines-default.tex + my $self = shift; + + # blank line token + my $blankLineToken = $tokens{blanklines}; + + # if the body begins with 2 or more blank line tokens + if(${$self}{body} =~ m/^((?:$blankLineToken\R){2,})/s){ + + # remove them + ${$self}{body} =~ s/^((?:$blankLineToken\R)+)//s; + + # store + my $blank_line_tokens_at_beginning_of_body = $1; + + # and count them, for use after the indentation routine + ${$self}{blankLinesAtBeginning} = () = $blank_line_tokens_at_beginning_of_body =~ /$blankLineToken\R/sg + } + return; +} + +sub put_blank_lines_back_in_at_beginning{ + my $self = shift; + + # some bodies have blank lines at the beginning + if(${$self}{blankLinesAtBeginning}){ + for(my $i=0; $i<${$self}{blankLinesAtBeginning}; $i++){ + ${$self}{body} = $tokens{blanklines}.${$self}{body}; + } + } + return +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/LatexIndent/Verbatim.pm b/Master/texmf-dist/scripts/latexindent/LatexIndent/Verbatim.pm new file mode 100644 index 00000000000..f3804aaf94d --- /dev/null +++ b/Master/texmf-dist/scripts/latexindent/LatexIndent/Verbatim.pm @@ -0,0 +1,296 @@ +package LatexIndent::Verbatim; +# 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/. +# +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl +use strict; +use warnings; +use LatexIndent::Tokens qw/%tokens/; +use LatexIndent::GetYamlSettings qw/%masterSettings/; +use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active/; +use Data::Dumper; +use Exporter qw/import/; +our @EXPORT_OK = qw/put_verbatim_back_in find_verbatim_environments find_noindent_block find_verbatim_commands put_verbatim_commands_back_in/; +our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 +our $verbatimCounter; + +sub find_noindent_block{ + my $self = shift; + + # noindent block + $self->logger('looking for NOINDENTBLOCk environments (see noIndentBlock)','heading') if $is_t_switch_active; + $self->logger(Dumper(\%{$masterSettings{noIndentBlock}})) if($is_t_switch_active); + while( my ($noIndentBlock,$yesno)= each %{$masterSettings{noIndentBlock}}){ + if($yesno){ + $self->logger("looking for $noIndentBlock:$yesno environments") if $is_t_switch_active; + + my $noIndentRegExp = qr/ + ( + (?!<\\) + % + \h* # possible horizontal spaces + \\begin\{ + $noIndentBlock # environment name captured into $2 + \} # %* \begin{noindentblock} statement + ) + ( + .*? + ) # non-greedy match (body) + ( + (?!<\\) + % # % + \h* # possible horizontal spaces + \\end\{$noIndentBlock\} # \end{noindentblock} + ) # %* \end{<something>} statement + /sx; + + while( ${$self}{body} =~ m/$noIndentRegExp/sx){ + + # create a new Environment object + my $noIndentBlock = LatexIndent::Verbatim->new( begin=>$1, + body=>$2, + end=>$3, + name=>$noIndentBlock, + ); + + # give unique id + $noIndentBlock->create_unique_id; + + # verbatim children go in special hash + ${$self}{verbatim}{${$noIndentBlock}{id}}=$noIndentBlock; + + # log file output + $self->logger("NOINDENTBLOCK environment found: $noIndentBlock") if $is_t_switch_active; + + # remove the environment block, and replace with unique ID + ${$self}{body} =~ s/$noIndentRegExp/${$noIndentBlock}{id}/sx; + + $self->logger("replaced with ID: ${$noIndentBlock}{id}") if $is_t_switch_active; + } + } else { + $self->logger("*not* looking for $noIndentBlock as $noIndentBlock:$yesno") if $is_t_switch_active; + } + } + return; +} + +sub find_verbatim_environments{ + my $self = shift; + + # verbatim environments + $self->logger('looking for VERBATIM environments (see verbatimEnvironments)','heading') if $is_t_switch_active; + $self->logger(Dumper(\%{$masterSettings{verbatimEnvironments}})) if($is_t_switch_active); + while( my ($verbEnv,$yesno)= each %{$masterSettings{verbatimEnvironments}}){ + if($yesno){ + $self->logger("looking for $verbEnv:$yesno environments") if $is_t_switch_active; + + my $verbatimRegExp = qr/ + ( + \\begin\{ + $verbEnv # environment name captured into $1 + \} # \begin{<something>} statement + ) + ( + .*? + ) # any character, but not \\begin + ( + \\end\{$verbEnv\}# \end{<something>} statement + ) + /sx; + + while( ${$self}{body} =~ m/$verbatimRegExp/sx){ + + # create a new Environment object + my $verbatimBlock = LatexIndent::Verbatim->new( begin=>$1, + body=>$2, + end=>$3, + name=>$verbEnv, + ); + # give unique id + $verbatimBlock->create_unique_id; + + # verbatim children go in special hash + ${$self}{verbatim}{${$verbatimBlock}{id}}=$verbatimBlock; + + # log file output + $self->logger("VERBATIM environment found: $verbEnv") if $is_t_switch_active; + + # remove the environment block, and replace with unique ID + ${$self}{body} =~ s/$verbatimRegExp/${$verbatimBlock}{id}/sx; + + $self->logger("replaced with ID: ${$verbatimBlock}{id}") if $is_t_switch_active; + } + } else { + $self->logger("*not* looking for $verbEnv as $verbEnv:$yesno") if $is_t_switch_active; + } + } + return; +} + +sub find_verbatim_commands{ + my $self = shift; + + # verbatim commands need to be treated separately to verbatim environments; + # note that, for example, we could quite reasonably have \lstinline!%!, which + # would need to be found *before* trailing comments have been removed. Similarly, + # verbatim commands need to be put back in *after* trailing comments have been put + # back in + $self->logger('looking for VERBATIM commands (see verbatimCommands)','heading'); + $self->logger(Dumper(\%{$masterSettings{verbatimCommands}})) if($is_tt_switch_active); + while( my ($verbCommand,$yesno)= each %{$masterSettings{verbatimCommands}}){ + if($yesno){ + $self->logger("looking for $verbCommand:$yesno Commands") if $is_t_switch_active; + + my $verbatimCommandRegExp = qr/ + ( + \\$verbCommand + \h* + ) # name of command into $1 + ( + \[ + (?: + (?! + (?:(?<!\\)\[) + ). + )*? # not including [, but \[ ok + (?<!\\) # not immediately pre-ceeded by \ + \] # [optional arguments] + \h* + )? # opt arg into $2 + ( + . + ) # delimiter into $3 + ( + .*? + ) # body into $4 + \3 + /mx; + + while( ${$self}{body} =~ m/$verbatimCommandRegExp/){ + + # create a new Environment object + my $verbatimCommand = LatexIndent::Verbatim->new( begin=>$1.($2?$2:q()).$3, + body=>$4, + end=>$3, + name=>$verbCommand, + optArg=>$2?$2:q(), + ); + # give unique id + $verbatimCommand->create_unique_id; + + # output, if desired + $self->logger(Dumper($verbatimCommand),'ttrace') if($is_tt_switch_active); + + # verbatim children go in special hash + ${$self}{verbatimCommands}{${$verbatimCommand}{id}}=$verbatimCommand; + + # log file output + $self->logger("VERBATIM command found: $verbCommand") if $is_t_switch_active; + + # remove the environment block, and replace with unique ID + ${$self}{body} =~ s/$verbatimCommandRegExp/${$verbatimCommand}{id}/sx; + + $self->logger("replaced with ID: ${$verbatimCommand}{id}") if $is_t_switch_active; + } + } else { + $self->logger("*not* looking for $verbCommand as $verbCommand:$yesno") if $is_t_switch_active; + } + } + return; + +} + +sub put_verbatim_back_in { + my $self = shift; + + # if there are no verbatim children, return + return unless(${$self}{verbatim}); + + # search for environments/commands + $self->logger('Putting verbatim back in, here is the pre-processed body:','heading') if $is_t_switch_active; + $self->logger(${$self}{body}) if($is_t_switch_active); + + # loop through document children hash + while( (scalar keys %{${$self}{verbatim}})>0 ){ + while( my ($key,$child)= each %{${$self}{verbatim}}){ + if(${$self}{body} =~ m/${$child}{id}/mx){ + + # replace ids with body + ${$self}{body} =~ s/${$child}{id}/${$child}{begin}${$child}{body}${$child}{end}/; + + # log file info + $self->logger('Body now looks like:','heading') if $is_tt_switch_active; + $self->logger(${$self}{body},'ttrace') if($is_tt_switch_active); + + # delete the hash so it won't be operated upon again + delete ${$self}{verbatim}{${$child}{id}}; + $self->logger("deleted key") if $is_t_switch_active; + } + } + } + + # logfile info + $self->logger("Number of children:",'heading') if $is_t_switch_active; + $self->logger(scalar keys %{${$self}{verbatim}}) if $is_t_switch_active; + $self->logger('Post-processed body:','heading') if $is_t_switch_active; + $self->logger(${$self}{body}) if($is_t_switch_active); + return; +} + +sub put_verbatim_commands_back_in { + my $self = shift; + + # if there are no verbatim children, return + return unless(${$self}{verbatimCommands}); + + # search for environments/commands + $self->logger('Putting verbatim commands back in, here is the pre-processed body:','heading') if $is_t_switch_active; + $self->logger(${$self}{body}) if($is_t_switch_active); + + # loop through document children hash + while( (scalar keys %{${$self}{verbatimCommands}})>0 ){ + while( my ($key,$child)= each %{${$self}{verbatimCommands}}){ + if(${$self}{body} =~ m/${$child}{id}/mx){ + + # replace ids with body + ${$self}{body} =~ s/${$child}{id}/${$child}{begin}${$child}{body}${$child}{end}/; + + # log file info + $self->logger('Body now looks like:','heading') if $is_tt_switch_active; + $self->logger(${$self}{body},'ttrace') if($is_tt_switch_active); + + # delete the hash so it won't be operated upon again + delete ${$self}{verbatimCommands}{${$child}{id}}; + $self->logger("deleted key") if $is_t_switch_active; + } + } + } + + # logfile info + $self->logger("Number of children:",'heading'); + $self->logger(scalar keys %{${$self}{verbatimCommands}}); + $self->logger('Post-processed body:','heading') if $is_t_switch_active; + $self->logger(${$self}{body}) if($is_t_switch_active); + return; +} + +sub create_unique_id{ + my $self = shift; + + $verbatimCounter++; + ${$self}{id} = "$tokens{verbatim}$verbatimCounter$tokens{endOfToken}"; + return; +} + +1; diff --git a/Master/texmf-dist/scripts/latexindent/defaultSettings.yaml b/Master/texmf-dist/scripts/latexindent/defaultSettings.yaml index ef620846f4f..20040817b12 100755 --- a/Master/texmf-dist/scripts/latexindent/defaultSettings.yaml +++ b/Master/texmf-dist/scripts/latexindent/defaultSettings.yaml @@ -1,5 +1,5 @@ # -# defaultSettings.yaml for latexindent.pl, +# defaultSettings.yaml for latexindent.pl, version 3.0 # a script that aims to # beautify .tex, .sty, .cls files # @@ -13,8 +13,17 @@ # # Please read the manual first to understand what each switch does :) -# Default value of indentation -defaultIndent: "\t" +# latexindent can be called without a file extension, +# e.g, simply +# latexindent myfile +# in which case the choice of file extension is chosen +# according to the choices made in fileExtensionPreference +# Other file extensions can be added. +fileExtensionPreference: + .tex: 1 + .sty: 2 + .cls: 3 + .bib: 4 # default file extension of backup file (if original is overwritten with -w switch) # for example, if your .tex file is called @@ -50,19 +59,62 @@ maxNumberOfBackUps: 0 # the back up will be written to myfile.bak4 cycleThroughBackUps: 0 +# preferences for information displayed in the log file +logFilePreferences: + showEveryYamlRead: 1 + showAmalgamatedSettings: 0 + endLogFileWith: '--------------' + showGitHubInfoFooter: 1 + +# verbatim environments- environments specified +# in this hash table will not be changed at all! +verbatimEnvironments: + verbatim: 1 + lstlisting: 1 + +# verbatim commands such as \verb! body !, \lstinline$something else$ +verbatimCommands: + verb: 1 + lstinline: 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 + +# remove trailing whitespace from all lines +removeTrailingWhitespace: + beforeProcessing: 0 + afterProcessing: 1 + +# \begin{document} and \end{document} are treated differently +# by latexindent within filecontents environments +fileContentsEnvironments: + filecontents: 1 + filecontents*: 1 + # indent preamble indentPreamble: 0 -# always look for split { }, which means that the user doesn't -# have to complete checkunmatched, checkunmatchedELSE -alwaysLookforSplitBraces: 1 +# assume no preamble in cls, sty, by default +lookForPreamble: + .tex: 1 + .sty: 0 + .cls: 0 + .bib: 0 -# always look for split [ ], which means that the user doesn't -# have to complete checkunmatchedbracket -alwaysLookforSplitBrackets: 1 +# some preambles can contain \begin and \end statements +# that are not in their 'standard environment block', for example, +# consider the following key = values: +# preheadhook={\begin{mdframed}[style=myframedstyle]}, +# postfoothook=\end{mdframed}, +preambleCommandsBeforeEnvironments: 0 -# remove trailing whitespace from all lines -removeTrailingWhitespace: 0 +# Default value of indentation +defaultIndent: "\t" # environments that have tab delimiters, add more # as needed @@ -91,48 +143,43 @@ lookForAlignDelims: dcases: 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: " " - item: " " - -# verbatim environments- environments specified -# in this hash table will not be changed at all! -verbatimEnvironments: - verbatim: 1 - lstlisting: 1 +# if you want the script to look for \item commands +# and format it, as follows (for example), +# \begin{itemize} +# \item content here +# next line is indented +# next line is indented +# \item another item +# \end{itemize} +# then populate indentAfterItems. See also itemNames +indentAfterItems: + itemize: 1 + enumerate: 1 + list: 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 want to use other names for your items (such as, for example, part) +# then populate them here- note that you can trick latexindent.pl +# into indenting all kinds of commands (within environments specified in +# indentAfterItems) using this technique. +itemNames: + item: 1 + myitem: 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 +# specialBeginEnd is mainly mathmode focus, although +# there's no restrictions +specialBeginEnd: + displayMath: + begin: '\\\[' + end: '\\\]' + lookForThis: 1 + inlineMath: + begin: '(?<!\$)(?<!\\)\$(?!\$)' + end: '(?<!\\)\$(?!\$)' + lookForThis: 1 + displayMathTeX: + begin: '\$\$' + end: '\$\$' + lookForThis: 1 # if you want to add indentation after # a heading, such as \part, \chapter, etc @@ -144,139 +191,220 @@ noAdditionalIndent: # or add your own title command indentAfterHeadings: part: - indent: 0 + indentAfterThisHeading: 0 level: 1 chapter: - indent: 0 + indentAfterThisHeading: 0 level: 2 section: - indent: 0 + indentAfterThisHeading: 0 level: 3 subsection: - indent: 0 + indentAfterThisHeading: 0 level: 4 subsection*: - indent: 0 + indentAfterThisHeading: 0 level: 4 subsubsection: - indent: 0 + indentAfterThisHeading: 0 level: 5 paragraph: - indent: 0 + indentAfterThisHeading: 0 level: 6 subparagraph: - indent: 0 + indentAfterThisHeading: 0 level: 7 -# if you want the script to look for \item commands -# and format it, as follows (for example), -# \begin{itemize} -# \item content here -# next line is indented -# next line is indented -# \item another item -# \end{itemize} -# then populate indentAfterItems. See also itemNames -indentAfterItems: - itemize: 1 - enumerate: 1 - list: 1 - -# if you want to use other names for your items (such as, for example, part) -# then populate them here- note that you can trick latexindent.pl -# into indenting all kinds of commands (within environments specified in -# indentAfterItems) using this technique. -itemNames: - item: 1 - myitem: 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 + document: 1 + frame: 0 -# if you want to indent if, else, fi constructs such as, for example, -# -# \ifnum#1=2 -# something -# \else -# something else -# \fi -# -# then populate them in constructIfElseFi -constructIfElseFi: - ifnum: 1 - ifdim: 1 - ifodd: 1 - ifvmode: 1 - ifhmode: 1 - ifmmode: 1 - ifinner: 1 - if: 1 - ifcat: 1 - ifx: 1 - ifvoid: 1 - ifeof: 1 - iftrue: 1 - ifcase: 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: " " + item: " " + myitem: " " -# latexindent can be called without a file extension, -# e.g, simply -# latexindent myfile -# in which case the choice of file extension is chosen -# according to the choices made in fileExtensionPreference -# Other file extensions can be added. -fileExtensionPreference: - .tex: 1 - .sty: 2 - .cls: 3 - .bib: 4 +# set noAdditionalIndent globally for codeblocks +noAdditionalIndentGlobal: + environments: 0 + commands: 1 + optionalArguments: 0 + mandatoryArguments: 0 + ifElseFi: 0 + items: 0 + keyEqualsValuesBracesBrackets: 0 + namedGroupingBracesBrackets: 0 + UnNamedGroupingBracesBrackets: 0 + specialBeginEnd: 0 + afterHeading: 0 + filecontents: 0 -# preferences for information displayed in the log file -logFilePreferences: - showEveryYamlRead: 1 - showAlmagamatedSettings: 0 - endLogFileWith: '--------------' - traceModeIncreaseIndent: '>>' - traceModeAddCurrentIndent: '||' - traceModeDecreaseIndent: '<<' - traceModeBetweenLines: "\n" +# set indentRules globally for codeblocks; these need +# to be horizontal spaces, if they are to be used +indentRulesGlobal: + environments: 0 + commands: 0 + optionalArguments: 0 + mandatoryArguments: 0 + ifElseFi: 0 + items: 0 + keyEqualsValuesBracesBrackets: 0 + namedGroupingBracesBrackets: 0 + UnNamedGroupingBracesBrackets: 0 + specialBeginEnd: 0 + afterHeading: 0 + filecontents: 0 -# \begin{document} and \end{document} are treated differently -# by latexindent within filecontents environments -fileContentsEnvironments: - filecontents: 1 - filecontents*: 1 +# command code block details +commandCodeBlocks: + roundParenthesesAllowed: 1 + stringsAllowedBetweenArguments: + node: 1 + at: 1 + to: 1 + decoration: 1 + ++: 1 + --: 1 - -# *** NOTE *** -# If you have specified alwaysLookforSplitBraces: 1 -# and alwaysLookforSplitBrackets: 1 then you don't need -# to worry about completing +# modifyLineBreaks will only be searched if the -m +# switch is active +# BeginStartsOnOwnLine: 1 +# if a begin statement is not already on its own line, +# then, with -m active, it will be moved to its own line. # -# checkunmatched -# checkunmatchedELSE -# checkunmatchedbracket +# when set to 1, e.g +# some text some text \begin{myenvironment} +# will be changed to +# some text some text +# \begin{myenvironment} +# when set to -1, e.g +# some text some text +# \begin{myenvironment} +# will be changed to +# some text some text \begin{myenvironment} +# when set to 0, the switch is ignored # -# 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 - +# BodyStartsOnOwnLine: 1 +# if body does not already start on its own line, then with +# -m active, it will be moved to its own line. +# +# when set to 1, e.g +# \begin{myenv}body text body text +# will be changed to +# \begin{myenv} +# body text body text +# when set to -1, e.g +# \begin{myenv} +# body text body text +# will be changed to +# \begin{myenv}body text body text +# when set to 0, the switch is ignored +# +# EndStartsOnOwnLine: 1 +# if an end statement is not already on its own line, +# then, with -m active, it will be moved to its own line. +# +# when set to 1, e.g +# some text some text \end{myenvironment} +# will be changed to +# some text some text +# \end{myenvironment} +# when set to -1, e.g +# some text some text +# \end{myenvironment} +# will be changed to +# some text some text \end{myenvironment} +# when set to 0, the switch is ignored +# +# EndFinishesWithLineBreak: 1 +# this switch adjusts line breaks immmediately +# after an end statement (with -m active) +# +# when set to 1, e.g +# \end{myenvironment}some text some text +# will be changed to +# \end{myenvironment} +# some text some text +# when set to -1, e.g +# \end{myenvironment} +# some text some text +# will be changed to +# \end{myenvironment}some text some text +# when set to 0, the switch is ignored +# +# Naturally, you can specify settings for individual environments, +# commands, headings, etc, populate as you wish. +modifyLineBreaks: + preserveBlankLines: 1 + condenseMultipleBlankLinesInto: 1 + environments: + BeginStartsOnOwnLine: 0 + BodyStartsOnOwnLine: 0 + EndStartsOnOwnLine: 0 + EndFinishesWithLineBreak: 0 + equation*: + BeginStartsOnOwnLine: 0 + BodyStartsOnOwnLine: 0 + EndStartsOnOwnLine: 0 + EndFinishesWithLineBreak: 0 + ifElseFi: + IfStartsOnOwnLine: 0 + BodyStartsOnOwnLine: 0 + ElseStartsOnOwnLine: 0 + ElseFinishesWithLineBreak: 0 + FiStartsOnOwnLine: 0 + FiFinishesWithLineBreak: 0 + ifnum: + IfStartsOnOwnLine: 0 + BodyStartsOnOwnLine: 0 + ElseStartsOnOwnLine: 0 + ElseFinishesWithLineBreak: 0 + FiStartsOnOwnLine: 0 + FiFinishesWithLineBreak: 0 + commands: + CommandStartsOnOwnLine: 0 + CommandNameFinishesWithLineBreak: 0 + optionalArguments: + LSqBStartsOnOwnLine: 0 + OptArgBodyStartsOnOwnLine: 0 + RSqBStartsOnOwnLine: 0 + RSqBFinishesWithLineBreak: 0 + mandatoryArguments: + LCuBStartsOnOwnLine: 0 + MandArgBodyStartsOnOwnLine: 0 + RCuBStartsOnOwnLine: 0 + RCuBFinishesWithLineBreak: 0 + keyEqualsValuesBracesBrackets: + KeyStartsOnOwnLine: 0 + EqualsStartsOnOwnLine: 0 + EqualsFinishesWithLineBreak: 0 + items: + ItemStartsOnOwnLine: 0 + ItemFinishesWithLineBreak: 0 + namedGroupingBracesBrackets: + NameStartsOnOwnLine: 0 + NameFinishesWithLineBreak: 0 + specialBeginEnd: + SpecialBeginStartsOnOwnLine: 0 + SpecialBodyStartsOnOwnLine: 0 + SpecialEndStartsOnOwnLine: 0 + SpecialEndFinishesWithLineBreak: 0 diff --git a/Master/texmf-dist/scripts/latexindent/latexindent.pl b/Master/texmf-dist/scripts/latexindent/latexindent.pl index cdd979a8ccf..06adc58a1b8 100755 --- a/Master/texmf-dist/scripts/latexindent/latexindent.pl +++ b/Master/texmf-dist/scripts/latexindent/latexindent.pl @@ -11,57 +11,34 @@ # # See http://www.gnu.org/licenses/. # -# For details of how to use this file, please see readme.txt +# Chris Hughes, 2017 +# +# For all communication, please visit: https://github.com/cmhughes/latexindent.pl -# load packages/modules: assume strict and warnings are part of every perl distribution use strict; use warnings; +use FindBin; # help find defaultSettings.yaml +use Getopt::Long; # to get the switches/options/flags -# list of modules -my @listOfModules = ('FindBin','YAML::Tiny','File::Copy','File::Basename','Getopt::Long','File::HomeDir'); - -# check the other modules are available -foreach my $moduleName (@listOfModules) { - # references: - # http://stackoverflow.com/questions/251694/how-can-i-check-if-i-have-a-perl-module-before-using-it - # http://stackoverflow.com/questions/1917261/how-can-i-dynamically-include-perl-modules-without-using-eval - eval { - (my $file = $moduleName) =~ s|::|/|g; - require $file . '.pm'; - $moduleName->import(); - 1; - } or die "$moduleName Perl Module not currently installed; please install the module, and then try running latexindent.pl again; exiting"; -} - -# now that we have confirmed the modules are available, load them -use FindBin; # help find defaultSettings.yaml -use YAML::Tiny; # interpret defaultSettings.yaml and other potential settings files -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::Long; # to get the switches/options/flags -use File::HomeDir; # to get users home directory, regardless of OS +# use lib to make sure that @INC contains the latexindent directory +use lib $FindBin::RealBin; +use LatexIndent::Document; # get the options -my $overwrite; -my $outputToFile; -my $silentMode; -my $tracingMode; -my $tracingModeVeryDetailed; -my $readLocalSettings=0; -my $onlyDefault; -my $showhelp; -my $cruftDirectory; +my %switches = (readLocalSettings=>0); GetOptions ( - "overwrite|w"=>\$overwrite, -"outputfile|o"=>\$outputToFile, -"silent|s"=>\$silentMode, -"trace|t"=>\$tracingMode, -"ttrace|tt"=>\$tracingModeVeryDetailed, -"local|l:s"=>\$readLocalSettings, -"onlydefault|d"=>\$onlyDefault, -"help|h"=>\$showhelp, -"cruft|c=s"=>\$cruftDirectory, + "silent|s"=>\$switches{silentMode}, + "trace|t"=>\$switches{trace}, + "ttrace|tt"=>\$switches{ttrace}, + "local|l:s"=>\$switches{readLocalSettings}, + "onlydefault|d"=>\$switches{onlyDefault}, + "overwrite|w"=>\$switches{overwrite}, + "outputfile|o=s"=>\$switches{outputToFile}, + "modifylinebreaks|m"=>\$switches{modifyLineBreaks}, + "logfile|g=s"=>\$switches{logFileName}, + "help|h"=>\$switches{showhelp}, + "cruft|c=s"=>\$switches{cruftDirectory}, ); # check local settings doesn't interfer with reading the file; @@ -74,20 +51,9 @@ GetOptions ( # # In such circumstances, we correct the mistake by assuming that # the only argument is the file to be indented, and place it in @ARGV -if($readLocalSettings and scalar(@ARGV) < 1) { - push(@ARGV,$readLocalSettings); - $readLocalSettings = ''; -} - -# this can also happen if the script is called as -# -# latexindent.pl -o -l myfile.tex outputfile.tex -# -# in which case, the GetOptions routine mistakes myfile.tex -# as the optional parameter to the l flag. -if($readLocalSettings and scalar(@ARGV) < 2 and $outputToFile) { - unshift(@ARGV,$readLocalSettings); - $readLocalSettings = ''; +if($switches{readLocalSettings} and scalar(@ARGV) < 1) { + push(@ARGV,$switches{readLocalSettings}); + $switches{readLocalSettings} = ''; } # default value of readLocalSettings @@ -95,2054 +61,10 @@ if($readLocalSettings and scalar(@ARGV) < 2 and $outputToFile) { # latexindent -l myfile.tex # # means that we wish to use localSettings.yaml -if(defined($readLocalSettings) and ($readLocalSettings eq '')){ - $readLocalSettings = 'localSettings.yaml'; -} - -# detailed tracing mode also implies regular tracing mode -$tracingMode = $tracingModeVeryDetailed ? 1 : $tracingMode; - -# version number -my $versionNumber = "2.2"; - -# 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 <<ENDQUOTE -latexindent.pl version $versionNumber -usage: latexindent.pl [options] [file][.tex] - -h, --help - help (see the documentation for detailed instructions and examples) - -o, --outputfile - output to another file; sample usage - latexindent.pl -o myfile.tex outputfile.tex - -w, --overwrite - overwrite the current file- a backup will be made, but still be careful - -s, --silent - silent mode- no output will be given to the terminal - -t, --trace - tracing mode- verbose information given to the log file - -l, --local[=myyaml.yaml] - use localSettings.yaml (assuming it exists in the directory of your file); - alternatively, use myyaml.yaml, if it exists - -d, --onlydefault - ONLY use defaultSettings.yaml, ignore ALL user files - -c, --cruft=<cruft directory> - used to specify the location of backup files and indent.log -ENDQUOTE - ; - exit(2); -} - -# set up default for cruftDirectory using the one from the input file, -# unless it has been specified using -c="/some/directory" -$cruftDirectory=dirname $ARGV[0] unless(defined($cruftDirectory)); - -die "Could not find directory $cruftDirectory\nExiting, no indentation done." if(!(-d $cruftDirectory)); - -# we'll be outputting to the logfile and to standard output -my $logfile; -my $out = *STDOUT; - -# open the log file -open($logfile,">","$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 <<ENDQUOTE - -$FindBin::Script version $versionNumber, a script to indent .tex files -$FindBin::Script lives here: $FindBin::RealBin/ - -ENDQUOTE -; - -# latexindent.exe is a standalone executable, and caches -# the required perl modules onto the users system; they will -# only be displayed if the user specifies the trace option -if($FindBin::Script eq 'latexindent.exe' and !$tracingMode ) { -print $logfile <<ENDQUOTE -$FindBin::Script is a standalone script and caches the required perl modules -onto your system. If you'd like to see their location in your log file, indent.log, -call the script with the tracing option, e.g latexindent.exe -t myfile.tex - -ENDQUOTE -; -} - -# output location of modules -if($FindBin::Script eq 'latexindent.pl' or ($FindBin::Script eq 'latexindent.exe' and $tracingMode )) { - print $logfile "Modules are being loaded from the following directories:\n "; - foreach my $moduleName (@listOfModules) { - (my $file = $moduleName) =~ s|::|/|g; - require $file . '.pm'; - print $logfile "\t",$INC{$file .'.pm'},"\n"; - } -} - -# a quick options check -if($outputToFile and $overwrite) { - print $logfile <<ENDQUOTE - -WARNING: -\t You have called latexindent.pl with both -o and -w -\t -o (output to file) will take priority, and -w (over write) will be ignored - -ENDQUOTE -; - $overwrite = 0; -} - -# can't call the script with MORE THAN 2 files -if(scalar(@ARGV)>2) { - for my $fh ($out,$logfile) {print $fh <<ENDQUOTE - -ERROR: -\t You're calling latexindent.pl with more than two file names -\t The script can take at MOST two file names, but you -\t need to call it with the -o switch; for example - -\t latexindent.pl -o originalfile.tex outputfile.tex - -No indentation done :( -Exiting... -ENDQUOTE - }; - exit(2); -} - -# don't call the script with 2 files unless the -o flag is active -if(!$outputToFile and scalar(@ARGV)==2) -{ -for my $fh ($out,$logfile) { -print $fh <<ENDQUOTE - -ERROR: -\t You're calling latexindent.pl with two file names, but not the -o flag. -\t Did you mean to use the -o flag ? - -No indentation done :( -Exiting... -ENDQUOTE -}; - exit(2); -} - -# if the script is called with the -o switch, then check that -# a second file is present in the call, e.g -# latexindent.pl -o myfile.tex output.tex -if($outputToFile and scalar(@ARGV)==1) { - for my $fh ($out,$logfile) {print $fh <<ENDQUOTE -ERROR: When using the -o flag you need to call latexindent.pl with 2 arguments - -latexindent.pl -o "$ARGV[0]" [needs another name here] - -No indentation done :( -Exiting... -ENDQUOTE -}; - exit(2); +if(defined($switches{readLocalSettings}) and ($switches{readLocalSettings} eq '')){ + $switches{readLocalSettings} = 'localSettings.yaml'; } -# yaml work -print $logfile "YAML files:\n"; - -# Read in defaultSettings.YAML file -my $defaultSettings = YAML::Tiny->new; - -# Open defaultSettings.yaml -$defaultSettings = YAML::Tiny->read( "$FindBin::RealBin/defaultSettings.yaml" ); -print $logfile "\tReading defaultSettings.yaml from $FindBin::RealBin/defaultSettings.yaml\n\n" if($defaultSettings); - -# if latexindent.exe is invoked from TeXLive, then defaultSettings.yaml won't be in -# the same directory as it; we need to navigate to it -if(!$defaultSettings) { - $defaultSettings = YAML::Tiny->read( "$FindBin::RealBin/../../texmf-dist/scripts/latexindent/defaultSettings.yaml"); - print $logfile "\tReading defaultSettings.yaml (2nd attempt, TeXLive, Windows) from $FindBin::RealBin/../../texmf-dist/scripts/latexindent/defaultSettings.yaml\n\n" if($defaultSettings); -} - -# if both of the above attempts have failed, we need to exit -if(!$defaultSettings) { - for my $fh ($out,$logfile) { - print $fh <<ENDQUOTE - ERROR There seems to be a yaml formatting error in defaultSettings.yaml - Please check it for mistakes- you can find a working version at https://github.com/cmhughes/latexindent.pl - if you would like to overwrite your current version - - Exiting, no indendation done. -ENDQUOTE -}; - exit(2); -} - -# the MASTER settings will initially be from defaultSettings.yaml -# and we update them with USER settings (if any) below -my %masterSettings = %{$defaultSettings->[0]}; - -# 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 indentconfig.yaml doesn't exist, check for the hidden file, .indentconfig.yaml -$indentconfig = File::HomeDir->my_home . "/.indentconfig.yaml" if(! -e $indentconfig); - -if ( -e $indentconfig and !$onlyDefault ) { - print $logfile "\tReading path information from $indentconfig\n"; - # if both indentconfig.yaml and .indentconfig.yaml exist - if ( -e File::HomeDir->my_home . "/indentconfig.yaml" and -e File::HomeDir->my_home . "/.indentconfig.yaml") { - print $logfile File::HomeDir->my_home,"/.indentconfig.yaml has been found, but $indentconfig takes priority\n"; - } elsif ( -e File::HomeDir->my_home . "/indentconfig.yaml" ) { - print $logfile "\tAlternatively, ",File::HomeDir->my_home,"/.indentconfig.yaml can be used\n"; - - } elsif ( -e File::HomeDir->my_home . "/.indentconfig.yaml" ) { - print $logfile "\tAlternatively, ",File::HomeDir->my_home,"/indentconfig.yaml can be used\n"; - } - - # read the absolute paths from indentconfig.yaml - $userSettings = YAML::Tiny->read( "$indentconfig" ); - - # integrity check - if($userSettings) { - print $logfile "\t",Dump \%{$userSettings->[0]}; - print $logfile "\n"; - @absPaths = @{$userSettings->[0]->{paths}}; - } else { - print $logfile <<ENDQUOTE -WARNING: $indentconfig - contains some invalid .yaml formatting- unable to read from it. - No user settings loaded. -ENDQUOTE -; - } -} else { - if($onlyDefault) { - print $logfile "\tOnly default settings requested, not reading USER settings from $indentconfig\n"; - print $logfile "\tIgnoring $readLocalSettings\n" if($readLocalSettings); - $readLocalSettings = 0; - } else { - # give the user instructions on where to put indentconfig.yaml or .indentconfig.yaml - print $logfile "\tHome directory is ",File::HomeDir->my_home,"\n"; - print $logfile "\tTo specify user settings you would put indentconfig.yaml here: \n\t",File::HomeDir->my_home,"/indentconfig.yaml\n\n"; - print $logfile "\tAlternatively, you can use the hidden file .indentconfig.yaml as: \n\t",File::HomeDir->my_home,"/.indentconfig.yaml\n\n"; - } -} - -# get information about LOCAL settings, assuming that $readLocalSettings exists -my $directoryName = dirname $ARGV[0]; - -# add local settings to the paths, if appropriate -if ( (-e "$directoryName/$readLocalSettings") and $readLocalSettings and !(-z "$directoryName/$readLocalSettings")) { - print $logfile "\tAdding $directoryName/$readLocalSettings to paths\n\n"; - push(@absPaths,"$directoryName/$readLocalSettings"); -} elsif ( !(-e "$directoryName/$readLocalSettings") and $readLocalSettings) { - print $logfile "\tWARNING yaml file not found: \n\t$directoryName/$readLocalSettings not found\n"; - print $logfile "\t\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 "\tReading USER settings from $settings\n"; - $userSettings = YAML::Tiny->read( "$settings" ); - - # if we can read userSettings - if($userSettings) { - # update the MASTER setttings to include updates from the userSettings - while(my($userKey, $userValue) = each %{$userSettings->[0]}) { - # the update approach is slightly different for hashes vs scalars/arrays - if (ref($userValue) eq "HASH") { - while(my ($userKeyFromHash,$userValueFromHash) = each %{$userSettings->[0]{$userKey}}) { - $masterSettings{$userKey}{$userKeyFromHash} = $userValueFromHash; - } - } else { - $masterSettings{$userKey} = $userValue; - } - } - # output settings to $logfile - if($masterSettings{logFilePreferences}{showEveryYamlRead}){ - print $logfile Dump \%{$userSettings->[0]}; - print $logfile "\n"; - } else { - print $logfile "\t\tNot showing settings in the log file, see showEveryYamlRead.\n"; - } - } 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" - } - } -} - -# some people may wish to see showAlmagamatedSettings -# which details the overall state of the settings modified -# from the default in various user files -if($masterSettings{logFilePreferences}{showAlmagamatedSettings}){ - print $logfile "Almagamated/overall settings to be used:\n"; - print $logfile Dump \%masterSettings ; -} - -# scalar variables -my $defaultIndent = $masterSettings{defaultIndent}; -my $alwaysLookforSplitBraces = $masterSettings{alwaysLookforSplitBraces}; -my $alwaysLookforSplitBrackets = $masterSettings{alwaysLookforSplitBrackets}; -my $backupExtension = $masterSettings{backupExtension}; -my $indentPreamble = $masterSettings{indentPreamble}; -my $onlyOneBackUp = $masterSettings{onlyOneBackUp}; -my $maxNumberOfBackUps = $masterSettings{maxNumberOfBackUps}; -my $removeTrailingWhitespace = $masterSettings{removeTrailingWhitespace}; -my $cycleThroughBackUps = $masterSettings{cycleThroughBackUps}; - -# hash variables -my %lookForAlignDelims= %{$masterSettings{lookForAlignDelims}}; -my %indentRules= %{$masterSettings{indentRules}}; -my %verbatimEnvironments= %{$masterSettings{verbatimEnvironments}}; -my %noIndentBlock= %{$masterSettings{noIndentBlock}}; -my %checkunmatched= %{$masterSettings{checkunmatched}}; -my %checkunmatchedELSE= %{$masterSettings{checkunmatchedELSE}}; -my %checkunmatchedbracket= %{$masterSettings{checkunmatchedbracket}}; -my %noAdditionalIndent= %{$masterSettings{noAdditionalIndent}}; -my %indentAfterHeadings= %{$masterSettings{indentAfterHeadings}}; -my %indentAfterItems= %{$masterSettings{indentAfterItems}}; -my %itemNames= %{$masterSettings{itemNames}}; -my %constructIfElseFi= %{$masterSettings{constructIfElseFi}}; -my %fileExtensionPreference= %{$masterSettings{fileExtensionPreference}}; -my %fileContentsEnvironments= %{$masterSettings{fileContentsEnvironments}}; - -# original name of file -my $fileName = $ARGV[0]; - -# sort the file extensions by preference -my @fileExtensions = sort { $fileExtensionPreference{$a} <=> $fileExtensionPreference{$b} } keys(%fileExtensionPreference); - -# get the base file name, allowing for different extensions (possibly no extension) -my ($dir, $name, $ext) = fileparse($fileName, @fileExtensions); - -# quick check to make sure given file type is supported -if( -e $ARGV[0] and !$ext ){ -for my $fh ($out,$logfile) {print $fh <<ENDQUOTE -The file $ARGV[0] exists , but the extension does not correspond to any given in fileExtensionPreference; -consinder updating fileExtensionPreference. - -Exiting, no indentation done. -ENDQUOTE -}; -exit(2); -} - -# if no extension, search according to fileExtensionPreference -if (!$ext) { - print $logfile "File extension work:\n"; - print $logfile "\tlatexindent called to act upon $fileName with an, as yet, unrecognised file extension;\n"; - print $logfile "\tsearching for file with an extension in the following order (see fileExtensionPreference):\n\t\t"; - print $logfile join("\n\t\t",@fileExtensions),"\n"; - my $fileFound = 0; - # loop through the known file extensions (see @fileExtensions) - foreach my $fileExt (@fileExtensions ){ - if ( -e $fileName.$fileExt ) { - print $logfile "\t",$fileName,$fileExt," found!\n"; - $fileName .= $fileExt; - print $logfile "\tUpdated $ARGV[0] to ",$fileName,"\n"; - $fileFound = 1; - last; - } - } - unless($fileFound){ - print $logfile "\tI couldn't find a match for $ARGV[0] in fileExtensionPreference (see defaultSettings.yaml)\n"; - foreach my $fileExt (@fileExtensions ){ - print $logfile "\t\tI searched for $ARGV[0]$fileExt\n"; - } - print $logfile "\tbut couldn't find any of them.\n"; - print $logfile "\tConsider updating fileExtensionPreference. \nError: Exiting, no indendation done."; - die "I couldn't find a match for $ARGV[0] in fileExtensionPreference.\nExiting, no indendation done.\n" - } - } else { - # if the file has a recognised extension, check that the file exists - unless( -e $ARGV[0] ){ - print $logfile "Error: I couldn't find $ARGV[0], are you sure it exists?. No indentation done. \nExiting.\n"; - die "Error: I couldn't find $ARGV[0], are you sure it exists?. Exiting.\n" ; - } - } - -# if we want to over write the current file create a backup first -if ($overwrite) { - print $logfile "\nBackup procedure:\n"; - # cruft directory - print $logfile "\tDirectory for backup files and indent.log: $cruftDirectory\n\n"; - - my $backupFile; - - # backup file name is the base name - $backupFile = basename($fileName,@fileExtensions); - - # 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) { - $onlyOneBackUp=0 ; - $maxNumberOfBackUps=-1; - } - - # if onlyOneBackUp is set, then the backup file will - # be overwritten each time - if($onlyOneBackUp) { - $backupFile .= $backupExtension; - print $logfile "\tcopying $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 .= $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"; - - # some users may wish to cycle through back up files, e.g: - # copy myfile.bak1 to myfile.bak0 - # copy myfile.bak2 to myfile.bak1 - # copy myfile.bak3 to myfile.bak2 - # - # current back up is stored in myfile.bak4 - if($cycleThroughBackUps) { - print $logfile "\t cycleThroughBackUps detected (see cycleThroughBackUps) \n"; - for(my $i=1;$i<=$maxNumberOfBackUps;$i++) { - # remove number from backUpFile - my $oldBackupFile = $backupFile; - $oldBackupFile =~ s/$backupExtension.*/$backupExtension/; - my $newBackupFile = $oldBackupFile; - - # add numbers back on - $oldBackupFile .= $i; - $newBackupFile .= $i-1; - - # check that the oldBackupFile exists - if(-e $oldBackupFile){ - print $logfile "\t\t copying $oldBackupFile to $newBackupFile \n"; - copy($oldBackupFile,$newBackupFile) or die "Could not write to backup file $backupFile. Please check permissions. Exiting.\n"; - } - } - } - - # rest maxNumberOfBackUps - $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 "\tBackup file: ",$backupFile,"\n"; - print $logfile "\tOverwriting 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 $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 $inFileContents=0; # $inFileContents: switch to determine if we're in a filecontents environment - -# array variables -my @lines; # @lines: stores the newly indented lines -my @mainfile; # @mainfile: stores input file; used to - # grep for \documentclass - -# array of hashes, containing details of commands & environments -my @masterIndentationArrayOfHashes; - -# 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, $fileName) or die "Could not open input file, $fileName"; - @mainfile=<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, $fileName) or die "Could not open input file, $fileName"; - -# loop through the lines in the INPUT file -while(<MAINFILE>) { - # increment the line counter - $lineCounter++; - - # very detailed output to logfile - if($tracingModeVeryDetailed){ - if( @masterIndentationArrayOfHashes){ - print $logfile "\nLine $lineCounter\t (detailed trace) indentation hash: \n" if($tracingMode); - for my $href ( @masterIndentationArrayOfHashes) { - print $logfile Dump \%{$href}; - } - } - } - - # tracing mode - print $logfile $masterSettings{logFilePreferences}{traceModeBetweenLines} 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/^\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\}/ and !$inFileContents and !$inverbatim) { - $inpreamble = 0; - - # tracing mode - print $logfile "Line $lineCounter\t \\begin{document} found, switching indentation searches on. \n" if($tracingMode); - } else { - # tracing mode - if($inpreamble) { - print $logfile "Line $lineCounter\t still in PREAMBLE, leaving exisiting leading space (see indentPreamble)\n" if($tracingMode); - } elsif($inverbatim) { - print $logfile "Line $lineCounter\t in VERBATIM-LIKE environment, leaving exisiting leading space\n" if($tracingMode); - } elsif($inIndentBlock) { - print $logfile "Line $lineCounter\t in NO INDENT BLOCK, leaving exisiting leading space\n" if($tracingMode); - } - } - } - - # \END{ENVIRONMENTS}, or CLOSING } or CLOSING ] - # \END{ENVIRONMENTS}, or CLOSING } or CLOSING ] - # \END{ENVIRONMENTS}, or CLOSING } or CLOSING ] - - # check to see if we're ending a filecontents environment - if( $_ =~ m/^\s*\\end\{(.*?)\}/ and $fileContentsEnvironments{$1} and $inFileContents){ - print $logfile "Line $lineCounter\t Found END of filecontents environment (see fileContentsEnvironments)\n" if($tracingMode); - $inFileContents = 0; - } - - # set the delimiters switch - $delimiters = @masterIndentationArrayOfHashes?$masterIndentationArrayOfHashes[-1]{alignmentDelimiters}:0; - - if($inverbatim){ - print $logfile "Line $lineCounter\t $masterSettings{logFilePreferences}{traceModeDecreaseIndent} PHASE 1: in VERBATIM-LIKE environment, looking for $masterIndentationArrayOfHashes[-1]{end} \n" if($tracingMode); - } elsif($inIndentBlock) { - print $logfile "Line $lineCounter\t in NO INDENT BLOCK, doing nothing\n" if($tracingMode); - } elsif($delimiters) { - print $logfile "Line $lineCounter\t $masterSettings{logFilePreferences}{traceModeDecreaseIndent} PHASE 1: in ALIGNMENT BLOCK environment, looking for $masterIndentationArrayOfHashes[-1]{end}\n" if($tracingMode); - } elsif($inpreamble and !$inFileContents) { - print $logfile "Line $lineCounter\t In preamble, looking for \\begin{document}\n" if($tracingMode); - } elsif($inpreamble and $inFileContents) { - print $logfile "Line $lineCounter\t In preamble, in filecontents environment\n" if($tracingMode); - } else { - print $logfile "Line $lineCounter\t $masterSettings{logFilePreferences}{traceModeDecreaseIndent} PHASE 1: looking for reasons to DECREASE indentation of CURRENT line \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 have %* \end{something} for alignment blocks - # outside of environments - &end_command_with_alignment(); - - # 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 or in a delimiter block - if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)) { - # 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 (?<!\\)% so that we don't match \% - if ( $_=~ m/(?<!\\)%.*/) { - s/((?<!\\)%.*)//; - $trailingcomments=$1; - - # tracing mode - print $logfile "Line $lineCounter\t Removed trailing comments to count braces and brackets: $1\n" if($tracingMode); - } - - # check to see if we're at the end of a \parbox, \marginpar - # or other split-across-lines command and check that - # we're not starting another command that has split braces (nesting) - &end_command_or_key_unmatched_braces(); - - # check to see if we're at the end of a command that splits - # [ ] across lines - &end_command_or_key_unmatched_brackets(); - - # check for a heading such as \chapter, \section, etc - &indent_heading(); - - # check for \item - &indent_item(); - - # check for \else or \fi - &indent_if_else_fi(); - - # add the trailing comments back to the end of the line - if(scalar($trailingcomments)) { - # some line break magic, http://stackoverflow.com/questions/881779/neatest-way-to-remove-linebreaks-in-perl - s/\R//; - $_ = $_ . $trailingcomments."\n" ; - - # tracing mode - print $logfile "Line $lineCounter\t counting braces/brackets complete\n" if($tracingMode); - print $logfile "Line $lineCounter\t Adding trailing comments back on: $trailingcomments\n" if($tracingMode); - - # empty the trailingcomments - $trailingcomments=''; - - } - # remove trailing whitespace - if ($removeTrailingWhitespace) { - print $logfile "Line $lineCounter\t removing trailing whitespace (see removeTrailingWhitespace)\n" if ($tracingMode); - s/\s+$/\n/; - } - } - - # ADD CURRENT LEVEL OF INDENTATION - # ADD CURRENT LEVEL OF INDENTATION - # ADD CURRENT LEVEL OF INDENTATION - # (unless we're in a delimiter-aligned block) - if(!$delimiters) { - # make sure we're not in a verbatim block or in the preamble - if($inverbatim or $inpreamble or $inIndentBlock) { - # just push the current line as is - push(@lines,$_); - } else { - # add current value of indentation to the current line - # and output it - # unless this would only create trailing whitespace and the - # corresponding option is set - unless ($_ =~ m/^$/ and $removeTrailingWhitespace){ - $_ = ¤t_indentation().$_; - } - push(@lines,$_); - # tracing mode - print $logfile "Line $lineCounter\t $masterSettings{logFilePreferences}{traceModeAddCurrentIndent} PHASE 2: Adding current level of indentation: ",¤t_indentation_names(),"\n" if($tracingMode); - } - } else { - # output to @block (within masterIndentationArrayOfHashes) if we're in a delimiter block - push(@{$masterIndentationArrayOfHashes[-1]{block}},$_); - - # tracing mode - print $logfile "Line $lineCounter\t In delimeter block ($masterIndentationArrayOfHashes[-1]{name}), waiting for block formatting\n" if($tracingMode); - } - - # \BEGIN{ENVIRONMENT} or OPEN { or OPEN [ - # \BEGIN{ENVIRONMENT} or OPEN { or OPEN [ - # \BEGIN{ENVIRONMENT} or OPEN { or OPEN [ - - # check to see if we're beginning a filecontents environment - if( ($_ =~ m/^\s*\\begin\{(.*?)\}/ and $fileContentsEnvironments{$1} and !$inverbatim)){ - print $logfile "Line $lineCounter\t Found filecontents environment (see fileContentsEnvironments)\n" if($tracingMode); - $inFileContents = 1; - } - - # only check for new environments or commands if we're - # not in a verbatim-like environment or in the preamble - # or in a noIndentBlock, or delimiter block - if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)) { - - print $logfile "Line $lineCounter\t $masterSettings{logFilePreferences}{traceModeIncreaseIndent} PHASE 3: looking for reasons to INCREASE indentation of SUBSEQUENT lines \n" if($tracingMode); - - # check if we are in a - # % \begin{noindent} - # block; this is similar to a verbatim block, the user - # may not want some blocks of code to be touched - # - # IMPORTANT: this needs to go before the trailing comments - # are removed! - &at_beg_noindent(); - - # check for - # %* \begin{tabular} - # which might be used to align blocks that contain delimeters that - # are NOT contained in an alignment block in the usual way, e.g - # \matrix{ - # %* \begin{tabular} - # 1 & 2 \\ - # 3 & 4 \\ - # %* \end{tabular} - # } - &begin_command_with_alignment(); - if(@masterIndentationArrayOfHashes){ - $delimiters = $masterIndentationArrayOfHashes[-1]{alignmentDelimiters}||0; - } - - # remove trailing comments so that any {, }, [, ] - # that are found after % are not counted - # - # note that these lines are already in @lines, so we - # can remove the trailing comments WITHOUT having - # to put them back in - # - # Note that this won't match \% - s/(?<!\\)%.*// if( $_=~ m/(?<!\\)%.*/); - - # tracing mode - print $logfile "Line $lineCounter\t Removing trailing comments for brace count (line is already stored)\n" if($tracingMode); - - # check to see if we have \begin{something} or \[ - &at_beg_of_env_or_eq() if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)); - if(@masterIndentationArrayOfHashes){ - $delimiters = $masterIndentationArrayOfHashes[-1]{alignmentDelimiters}||0; - } - - # check to see if we have \parbox, \marginpar, or - # something similar that might split braces {} across lines, - # specified in %checkunmatched hash table - &start_command_or_key_unmatched_braces() if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)); - - # check for an else statement (braces, not \else) - &check_for_else() if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)); - - # check for a command that splits [] across lines - &start_command_or_key_unmatched_brackets(); - - # check for a heading - &indent_after_heading() if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)); - - # check for \item - &indent_after_item() if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)); - - # check for \if or \else command - &indent_after_if_else_fi() if(!($inverbatim or $inpreamble or $inIndentBlock or $delimiters)); - - # tracing mode - if($tracingMode){ - if(scalar(@masterIndentationArrayOfHashes)){ - print $logfile "Line $lineCounter\t Indentation array: ",¤t_indentation_names(),"\n"; - } else { - print $logfile "Line $lineCounter\t Indentation array empty\n"; - } - } - } -} - -# close the main file -close(MAINFILE); - -# put line count information in the log file -print $logfile "Line Count of $fileName: ",scalar(@mainfile),"\n"; -print $logfile "Line Count of indented $fileName: ",scalar(@lines); -if(scalar(@mainfile) != scalar(@lines)) -{ - print $logfile <<ENDQUOTE -WARNING: \t line count of original file and indented file does -\t not match- consider reverting to a back up, see $backupExtension; -ENDQUOTE -; -} else { - print $logfile "\n\nLine counts of original file and indented file match.\n"; -} - -# output the formatted lines to the terminal -print @lines if(!$silentMode); - -# if -w is active then output to $ARGV[0] -if($overwrite) { - open(OUTPUTFILE,">",$fileName); - 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); - print $logfile "Output from indentation written to $ARGV[1].\n"; -} - -# final line of the logfil -print $logfile "\n",$masterSettings{logFilePreferences}{endLogFileWith}; - -# close the log file -close($logfile); - +my $document = LatexIndent::Document->new(name=>"masterdocument",fileName=>$ARGV[0],switches=>\%switches); +$document->latexindent; exit(0); - -sub indent_if_else_fi{ - # PURPOSE: set indentation of line that contains \else, \fi command - # - # - - # @masterIndentationArrayOfHashes could be empty -- if so, exit - return unless @masterIndentationArrayOfHashes; - return unless $constructIfElseFi{$masterIndentationArrayOfHashes[-1]{name}}; - - # look for \fi - if( $_ =~ m/^\s*\\fi/) { - # tracing mode - print $logfile "Line $lineCounter\t \\fi command found, matching: \\",$masterIndentationArrayOfHashes[-1]{name}, "\n" if($tracingMode); - &decrease_indent($masterIndentationArrayOfHashes[-1]{name}); - } - # look for \else or \or - elsif( $_ =~ m/^\s*\\else/ or $_ =~ m/^\s*\\or/ ) { - # tracing mode - print $logfile "Line $lineCounter\t \\else command found, matching: \\",$masterIndentationArrayOfHashes[-1]{name}, "\n" if($tracingMode); - print $logfile "Line $lineCounter\t decreasing indent, still looking for \\fi to match \\",¤t_indentation_names(), "\n" if($tracingMode); - - # finding an \else or \or command removes the *indentation*, but not the entry from the master hash - $masterIndentationArrayOfHashes[-1]{indent}=""; - } -} - -sub indent_after_if_else_fi{ - # PURPOSE: set indentation *after* \if construct such as - # - # \ifnum\x=2 - # <stuff> - # <stuff> - # \else - # <stuff> - # <stuff> - # \fi - # - # How to read /^\s*\\(if.*?)(\s|\\|\#) - # - # ^\s* begins with multiple spaces (possibly none) - # \\(if.*?)(\s|\\|\#) matches \if... up to either a - # space, a \, or a # - # Note: this won't match \if.*\fi - if( $_ =~ m/^\s*\\(if.*?)(\s|\\|\#)/ and $_ !~ m/^\s*\\(if.*?\\fi)/ and $constructIfElseFi{$1}) { - # tracing mode - print $logfile "Line $lineCounter\t ifelsefi construct found: $1 \n" if($tracingMode); - &increase_indent({name=>$1,type=>"ifelseif"}); - } elsif(@masterIndentationArrayOfHashes) { - if( ($_ =~ m/^\s*\\else/ or $_ =~ m/^\s*\\or/ ) and $constructIfElseFi{$masterIndentationArrayOfHashes[-1]{name}}) { - # tracing mode - print $logfile "Line $lineCounter\t setting indent *after* \\else or \\or command found for $masterIndentationArrayOfHashes[-1]{name} \n" if($tracingMode); - - # recover the indentation to be implemented *after* the \else or \or - $masterIndentationArrayOfHashes[-1]{indent}=$indentRules{$masterIndentationArrayOfHashes[-1]{name}}||$defaultIndent unless ($noAdditionalIndent{$masterIndentationArrayOfHashes[-1]{name}}); - } - } -} - -sub indent_item{ - # PURPOSE: when considering environments that can contain items, such - # as enumerate, itemize, etc, this subroutine sets the indentation for the item *itself* - - return unless(scalar(@masterIndentationArrayOfHashes)>1); - return unless $indentAfterItems{$masterIndentationArrayOfHashes[-2]{name}}; - - if( $_ =~ m/^\s*\\(.*?)(\[|\s)/ and $itemNames{$1}){ - # tracing mode - print $logfile "Line $lineCounter\t $1 found within ",$masterIndentationArrayOfHashes[-1]{name}," environment (see indentAfterItems and itemNames)\n" if($tracingMode); - if($itemNames{$masterIndentationArrayOfHashes[-1]{name}}) { - print $logfile "Line $lineCounter\t $1 found - neutralizing indentation from previous ",$masterIndentationArrayOfHashes[-1]{name},"\n" if($tracingMode); - &decrease_indent($1); - } - } - -} - -sub indent_after_item{ - # PURPOSE: Set the indentation *after* the item - # This matches a line that begins with - # - # \item - # \item[ - # \myitem - # \myitem[ - # - # or anything else specified in itemNames - # - return unless @masterIndentationArrayOfHashes; - return unless $indentAfterItems{$masterIndentationArrayOfHashes[-1]{name}}; - - if( $_ =~ m/^\s*\\(.*?)(\[|\s)/ - and $itemNames{$1}) { - # tracing mode - print $logfile "Line $lineCounter\t $1 found within ",$masterIndentationArrayOfHashes[-1]{name}," environment (see indentAfterItems and itemNames)\n" if($tracingMode); - &increase_indent({name=>$1,type=>"item"}); - } -} - -sub begin_command_with_alignment{ - # PURPOSE: This matches - # %* \begin{tabular} - # with any number of spaces (possibly none) between - # the * and \begin{noindent}. - # - # the comment symbol IS indended! - # - # This is to align blocks that contain delimeters that - # are NOT contained in an alignment block in the usual way, e.g - # \matrix{ - # %* \begin{tabular} - # 1 & 2 \\ - # 3 & 4 \\ - # %* \end{tabular} - # } - - if( $_ =~ m/^\s*%\*\s*\\begin\{(.*?)\}/ and $lookForAlignDelims{$1}) { - # increase the indentation - &increase_indent({name=>$1, - alignmentDelimiters=>1, - type=>"environment", - begin=>"\\begin{$1}", - end=>"\\end{$1}"}); - # tracing mode - print $logfile "Line $lineCounter\t Delimiter environment started: $1 (see lookForAlignDelims)\n" if($tracingMode); - } -} - -sub end_command_with_alignment{ - # PURPOSE: This matches - # %* \end{tabular} - # with any number of spaces (possibly none) between - # the * and \end{tabular} (or any other name used from - # lookFroAlignDelims) - # - # Note: the comment symbol IS indended! - # - # This is to align blocks that contain delimeters that - # are NOT contained in an alignment block in the usual way, e.g - # \matrix{ - # %* \begin{tabular} - # 1 & 2 \\ - # 3 & 4 \\ - # %* \end{tabular} - # } - return unless @masterIndentationArrayOfHashes; - return unless $masterIndentationArrayOfHashes[-1]{alignmentDelimiters}; - - if( $_ =~ m/^\s*%\*\s*\\end\{(.*?)\}/ and $lookForAlignDelims{$1}) { - # same subroutine used at the end of regular tabular, align, etc - # environments - if($delimiters) { - &print_aligned_block(); - &decrease_indent($1); - } else { - # tracing mode - print $logfile "Line $lineCounter\t FYI: did you mean to start a delimiter block on a previous line? \n" if($tracingMode); - print $logfile "Line $lineCounter\t perhaps using %* \\begin{$1}\n" if($tracingMode); - } - } -} - -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}}; - - # $previousHeadingLevel: scalar that stores which heading - # we are under: \part, \chapter, etc - my $previousHeadingLevel=0; - - # form an array of the headings available - my @headingStore=(); - foreach my $env (@masterIndentationArrayOfHashes){ - if($env->{type} eq 'heading'){ - push(@headingStore,$env->{name}); - # update heading level - $previousHeadingLevel= $env->{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}); - } - } - } -} - -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({name=>$1,type=>"heading",headinglevel=>$currentHeading{level}}) if($currentHeading{indent}); - } -} - -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 - # ((?<!\\)\[\s*) match [ possibly leading with spaces - # but it WON'T match \[ - - if ($_ =~ m/^\s*(\\)?(.*?)(\s*(?<!\\)\[)/ - and ($checkunmatchedbracket{$2} or $alwaysLookforSplitBrackets)) { - # store the command name, because $2 - # will not exist after the next match - my $commandname = $2; - my $matchedBRACKETS=0; - - # match [ but don't match \[ - $matchedBRACKETS++ while ($_ =~ /(?<!\\)\[/g); - # match ] but don't match \] - $matchedBRACKETS-- while ($_ =~ /(?<!\\)\]/g); - - # set the indentation - if($matchedBRACKETS != 0 ) { - # tracing mode - print $logfile "Line $lineCounter\t Found opening BRACKET [ $commandname\n" if($tracingMode); - - &increase_indent({name=>$commandname,matchedBRACKETS=>$matchedBRACKETS,type=>'splitBrackets'}); - } - } -} - -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 @masterIndentationArrayOfHashes, 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 - return unless @masterIndentationArrayOfHashes; - return unless ($masterIndentationArrayOfHashes[-1]{type} eq 'splitBrackets'); - print $logfile "Line $lineCounter\t Searching for closing BRACKET ] $masterIndentationArrayOfHashes[-1]{name}\n" if($tracingMode); - - if(!($_ =~ m/^\s*(\\)?(.*?)(\s*\[)/ - and ($checkunmatchedbracket{$2} or $alwaysLookforSplitBrackets)) - and $_ !~ m/^\s*%/) { - - # get the details of the most recent command name - my $commandname = $masterIndentationArrayOfHashes[-1]{name}; - my $matchedBRACKETS = $masterIndentationArrayOfHashes[-1]{matchedBRACKETS}; - - # match [ but don't match \[ - $matchedBRACKETS++ while ($_ =~ m/(?<!\\)\[/g); - - # match ] but don't match \] - $matchedBRACKETS-- while ($_ =~ m/(?<!\\)\]/g); - - # if we've matched up the BRACKETS then - # we can decrease the indent by 1 level - if($matchedBRACKETS == 0){ - # tracing mode - print $logfile "Line $lineCounter\t Found closing BRACKET ] $commandname\n" if($tracingMode); - - # decrease the indentation (if appropriate) - &decrease_indent($commandname); - } else { - # otherwise we need to enter the new value - # of $matchedBRACKETS and the value of $command - # back into storage - $masterIndentationArrayOfHashes[-1]{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 ($checkunmatched{$2} or $checkunmatchedELSE{$2} - or $alwaysLookforSplitBraces) - ) { - # store the command name, because $2 - # will not exist after the next match - my $commandname = $2; - my $matchedbraces=0; - - # by default, don't look for an else construct - my $lookforelse=$checkunmatchedELSE{$2}||0; - - # match { but don't match \{ - $matchedbraces++ while ($_ =~ /(?<!\\){/g); - - # match } but don't match \} - $matchedbraces-- while ($_ =~ /(?<!\\)}/g); - - # tracing mode - print $logfile "Line $lineCounter\t matchedbraces = $matchedbraces\n" if($tracingMode); - - # set the indentation - if($matchedbraces > 0 ) { - # tracing mode - print $logfile "Line $lineCounter\t Found opening BRACE { $commandname\n" if($tracingMode); - - &increase_indent({name=>$commandname, - matchedbraces=>$matchedbraces, - lookforelse=>$lookforelse, - countzeros=>0, - type=>"splitbraces"}); - } 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/(((?<!\\){)|((?<!\\)}))/g) { - - # store the match, either { or } - my $braceType = $1; - - # exit the loop if @masterIndentationArrayOfHashes[-1] is empty - last if(!@masterIndentationArrayOfHashes); - - # exit the loop if we're not looking for split braces - last if($masterIndentationArrayOfHashes[-1]{type} ne 'splitbraces'); - - # get the details of the most recent command name - $commandname = $masterIndentationArrayOfHashes[-1]{name}; - $matchedbraces = $masterIndentationArrayOfHashes[-1]{'matchedbraces'}; - my $countzeros = $masterIndentationArrayOfHashes[-1]{'countzeros'}; - $lookforelse= $masterIndentationArrayOfHashes[-1]{'lookforelse'}; - - $matchedbraces++ if($1 eq "{"); - $matchedbraces-- if($1 eq "}"); - - # update the matched braces count - $masterIndentationArrayOfHashes[-1]{matchedbraces} = $matchedbraces; - - # 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) { - $masterIndentationArrayOfHashes[-1]{'matchedbraces'} = $matchedbraces; - $masterIndentationArrayOfHashes[-1]{'countzeros'} = $countzeros; - $masterIndentationArrayOfHashes[-1]{'lookforelse'} = $lookforelse; - } - } - } - } - } -} - -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 @masterIndentationArrayOfHashes, 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 - return unless @masterIndentationArrayOfHashes; - return unless ($masterIndentationArrayOfHashes[-1]{type} eq 'splitbraces'); - print $logfile "Line $lineCounter\t Searching for closing BRACE } $masterIndentationArrayOfHashes[-1]{name}\n" if($tracingMode); - - if(!($_ =~ m/^\s*(\\)?(.*?)(\[|{|=|(\s*\\))/ - and ($checkunmatched{$2} or $checkunmatchedELSE{$2} or $alwaysLookforSplitBraces)) - and $_ !~ m/^\s*%/ - ) { - # keep matching { OR }, and don't match \{ or \} - while ($_ =~ m/(((?<!\\){)|((?<!\\)}))/g) { - # store the match, either { or } - my $braceType = $1; - - # exit the loop if @masterIndentationArrayOfHashes[-1] is empty - last if(!@masterIndentationArrayOfHashes); - - # exit the loop if we're not looking for split braces - last if($masterIndentationArrayOfHashes[-1]{type} ne 'splitbraces'); - - # get the details of the most recent command name - my $commandname = $masterIndentationArrayOfHashes[-1]{name}; - my $matchedbraces = $masterIndentationArrayOfHashes[-1]{matchedbraces}; - my $countzeros = $masterIndentationArrayOfHashes[-1]{countzeros}; - my $lookforelse= $masterIndentationArrayOfHashes[-1]{lookforelse}; - - $matchedbraces++ if($1 eq "{"); - $matchedbraces-- if($1 eq "}"); - - # update the matched braces count - $masterIndentationArrayOfHashes[-1]{matchedbraces} = $matchedbraces; - - # 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){ - $masterIndentationArrayOfHashes[-1]{'matchedbraces'} = $matchedbraces; - $masterIndentationArrayOfHashes[-1]{'countzeros'} = $countzeros; - $masterIndentationArrayOfHashes[-1]{'lookforelse'} = $lookforelse; - } - } - - if(@masterIndentationArrayOfHashes){ - if($masterIndentationArrayOfHashes[-1]{'type'} eq 'splitbraces'){ - # tracing mode - print $logfile "Line $lineCounter\t Searching for closing BRACE } $masterIndentationArrayOfHashes[-1]{name}\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 @masterIndentationArrayOfHashes, and if - # the line itself is not a command, or comment, - # and if it begins with { - - if(scalar(@masterIndentationArrayOfHashes) - and !($_ =~ m/^\s*(\\)?(.*?)(\[|{|=)/ - and ($checkunmatched{$2} or $checkunmatchedELSE{$2} - or $alwaysLookforSplitBraces)) - and $_ =~ m/^\s*{/ - and $_ !~ m/^\s*%/ - ) { - # get the details of the most recent command name - my $matchedbraces = $masterIndentationArrayOfHashes[-1]{'matchedbraces'}; - my $countzeros = $masterIndentationArrayOfHashes[-1]{'countzeros'}; - my $lookforelse= $masterIndentationArrayOfHashes[-1]{'lookforelse'}; - - # increase indentation - if($lookforelse and $countzeros==1) { - #&increase_indent($commandname); - } - - # put the array back together - $masterIndentationArrayOfHashes[-1]{'matchedbraces'} = $matchedbraces; - $masterIndentationArrayOfHashes[-1]{'countzeros'} = $countzeros; - $masterIndentationArrayOfHashes[-1]{'lookforelse'} = $lookforelse; - } -} - -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 - # \\begin{\\?(.*?)} \begin{something} where something could start - # with a backslash, e.g \my@env@ which can happen - # in a style or class file, for example - - 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({name=>$2, - type=>"environment", - begin=>"\\begin{$2}", - end=>"\\end{$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}; - } - } - } -} - -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 - # \] - # - # Note: environmentname can begin with a backslash - # which might happen in a sty or cls file. - # - # It also checks to see if the current environment - # had alignment delimiters; if so, we need to turn - # OFF the $delimiter switch - - return unless @masterIndentationArrayOfHashes; - print $logfile "Line $lineCounter\t looking for \\end{$masterIndentationArrayOfHashes[-1]{name}} \n" if($tracingMode); - - 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 if we're in an environment that is looking - # to indent after each \item - if(scalar(@masterIndentationArrayOfHashes) and $itemNames{$masterIndentationArrayOfHashes[-1]{name}}) { - &decrease_indent($masterIndentationArrayOfHashes[-1]{name}); - } - - # if we're at the end of an environment that receives no additional indent, log it, and move on - if($noAdditionalIndent{$1}){ - print $logfile "Line $lineCounter\t \\end{$1} finished a no-additional-indent environment (see noAdditionalIndent)\n" if($tracingMode); - } - - # some commands contain \end{environmentname}, which - # can cause a problem if \begin{environmentname} was not - # started previously; if @masterIndentationArrayOfHashes is empty, - # then we don't need to check for \end{environmentname} - if(@masterIndentationArrayOfHashes) { - # check to see if \end{environment} fits with most recent \begin{...} - my %previousEnvironment = %{$masterIndentationArrayOfHashes[-1]}; - - # check to see if we need to turn off alignment - # delimiters and output the current block - if($masterIndentationArrayOfHashes[-1]{alignmentDelimiters} and ($previousEnvironment{name} eq $1)) { - &print_aligned_block(); - } - - # tracing mode - print $logfile "Line $lineCounter\t \\end{environment} found: $1 \n" if($tracingMode and !$verbatimEnvironments{$1}); - - # check to see if \end{environment} fits with most recent \begin{...} - if($previousEnvironment{name} eq $1) { - # decrease the indentation (if appropriate) - print $logfile "Line $lineCounter\t removed $1 from Indentation array\n" if($tracingMode); - &decrease_indent($1); - } else { - # otherwise put the environment name back on the stack - print $logfile "Line $lineCounter\t WARNING: \\end{$1} found on its own line, not matched to \\begin{$previousEnvironment{name}}\n" unless ($delimiters or $inverbatim or $inIndentBlock or $1 eq "\\\]"); - } - - # need a special check for \[ and \] - if($1 eq "\\\]") { - &decrease_indent($1); - } - } - - # 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 !$inFileContents and !$inpreamble and !$delimiters and !$inverbatim and !$inIndentBlock and @masterIndentationArrayOfHashes) { - @masterIndentationArrayOfHashes=(); - - # tracing mode - if($tracingMode) { - print $logfile "Line $lineCounter\t \\end{$1} found, emptying indentation array \n" unless ($delimiters or $inverbatim or $inIndentBlock or $1 eq "\\\]"); - } - } - } -} - -sub print_aligned_block{ - # PURPOSE: this subroutine does a few things related - # to printing blocks of code that contain - # delimiters, such as align, tabular, etc - # - # It does the following - # - turns off delimiters switch - # - processes the block - # - deletes the block - $delimiters=0; - - # tracing mode - print $logfile "Line $lineCounter\t Delimiter body FINISHED: $masterIndentationArrayOfHashes[-1]{name}\n" if($tracingMode); - - # print the current FORMATTED block - my @block = &format_block(@{$masterIndentationArrayOfHashes[-1]{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 =¤t_indentation().$line; - } - push(@lines,$line); - } -} - -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: $masterIndentationArrayOfHashes[-1]{name}\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/((?<!\\)%.*$)/) { - $row =~ s/((?<!\\)%.*)/%TC/; - $trailingcomments=$1; - } - - # reset temporary counter - $currentNumberAmpersands=0; - - # count & in current row (exclude \&) - $currentNumberAmpersands++ while ($row =~ /(?<!\\)&/g); - - # store the ampersand count for future - push(@ampersandCount,$currentNumberAmpersands); - - # overwrite maximum count if the temp count is higher - $maxNumberAmpersands = $currentNumberAmpersands if($currentNumberAmpersands > $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/((?<!\\)%.*$)/) { - $row =~ s/((?<!\\)%.*)/%TC/; - $trailingcomments=$1; - } - - # separate the row at each &, but not at \& - @tmprow = split(/(?<!\\)&/,$row); - - # reset column counter - $aligncolcounter=-1; - - # loop through each column element - # removing leading and trailing space - foreach $column (@tmprow) { - # increment column counter - $aligncolcounter++; - - # remove leading and trailing space from element - $column =~ s/^\s+//; - $column =~ s/\s+$//; - - # assign string size to the array - $stringsize{$alignrowcounter.$aligncolcounter}=length($column); - if(length($column)==0){ - $column=" "; - } - - # put the row back together - if ($aligncolcounter ==0){ - $tmpstring = $column; - } else { - $tmpstring .= "&".$column; - } - } - - - # put $linebreak back on the string, now that - # the measurement has been done - $tmpstring .= $linebreak; - - # put trailing comments back on - if($trailingcomments) { - $tmpstring =~ s/%TC/$trailingcomments/; - } - - push(@tmpblock,$tmpstring); - } else { - # if there are no & then use the - # NOFORMATTING token - # remove leading space - s/^\s+//; - push(@tmpblock,$row."NOFORMATTING"); - } - } - - # calculate the maximum string size of each column - for($j=0;$j<=$aligncolcounter;$j++) { - $maxmcolstrlength=0; - for($i=0; $i<=$alignrowcounter;$i++) { - # make sure the stringsize is defined - if(defined $stringsize{$i.$j}) { - if ($stringsize{$i.$j}>$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/((?<!\\)%.*$)/) { - $row =~ s/((?<!\\)%.*)/%TC/; - $trailingcomments=$1; - } - - $tmpstring = sprintf($fmtstring,split(/(?<!\\)&/,$row)).$linebreak."\n"; - - # remove space before \\ if specified in alignDoubleBackSlash - if($masterIndentationArrayOfHashes[-1]{alignDoubleBackSlash}==0){ - print $logfile "\t\tLine $lineCounter\t removing space before \\\\ (see $masterIndentationArrayOfHashes[-1]{name} alignDoubleBackSlash)\n" if($tracingMode); - $tmpstring =~ s/\s*\\\\/\\\\/; - # some users may like to put a number of spaces before \\ - if($masterIndentationArrayOfHashes[-1]{spacesBeforeDoubleBackSlash}){ - my $spaceString; - for($j=1;$j<=$masterIndentationArrayOfHashes[-1]{spacesBeforeDoubleBackSlash};$j++) { - $spaceString .= ' '; - } - print $logfile "\t\tLine $lineCounter\t adding $masterIndentationArrayOfHashes[-1]{spacesBeforeDoubleBackSlash} ",$masterIndentationArrayOfHashes[-1]{spacesBeforeDoubleBackSlash}>1?"spaces":"space"," before \\\\ (see $masterIndentationArrayOfHashes[-1]{name} spacesBeforeDoubleBackSlash)\n" if($tracingMode); - $tmpstring =~ s/\\\\/$spaceString\\\\/; - } - } - - # put trailing comments back on - if($trailingcomments) { - $tmpstring =~ s/%TC/$trailingcomments/; - } - - # tracing mode - print $logfile "\t\tLine $lineCounter\t Found maximum number of & so aligning delimiters\n" if($tracingMode); - } - - # remove trailing whitespace - if ($removeTrailingWhitespace) { - print $logfile "\t\tLine $lineCounter\t removing trailing whitespace from delimiter aligned line\n" if ($tracingMode); - $tmpstring =~ s/\s+$/\n/; - } - - push(@formattedblock,$tmpstring); - - # increase the line counter - $lineCounter++; - } - - # return the formatted block - @formattedblock; -} - -sub increase_indent{ - # PURPOSE: Adjust the indentation - # of the current environment, command, etc; - # check that it's not an environment - # that doesn't want indentation. - - my %infoHash = %{pop(@_)}; - my $command = $infoHash{name}; - - # check for conflicting hash keys - &check_conflicting_keys($command); - - # quick check for verbatim Environment - if($inverbatim){ - print $logfile "Line $lineCounter\t currently inverbatim environment, not increasing indentation\n" if($tracingMode); - return; - } - - if($indentRules{$command}) { - # tracing mode - print $logfile "Line $lineCounter\t increasing indent using rule for $command (see indentRules)\n" if($tracingMode); - } else { - # default indentation - if(!($noAdditionalIndent{$command} or $verbatimEnvironments{$command})) { - # tracing mode - print $logfile "Line $lineCounter\t increasing indent using defaultIndent\n" if($tracingMode); - } elsif($noAdditionalIndent{$command}) { - # tracing mode - print $logfile "Line $lineCounter\t no additional indent added for $command (see noAdditionalIndent)\n" if($tracingMode); - } - } - - # add to the master array of hashes - push(@masterIndentationArrayOfHashes,\%infoHash); - - # handle the keys slightly different when dealing with environments or commands - if($infoHash{type} eq 'environment'){ - # environments - if(!$noAdditionalIndent{$command}){ - $masterIndentationArrayOfHashes[-1]{indent} = $indentRules{$command}||$defaultIndent; - } - # check to see if we need to look for alignment delimiters - if($lookForAlignDelims{$command}){ - # there are two ways to complete the lookForAlignDelims field, either as a scalar - # or as a hash, so that we can check for alignDoubleBackSlash. - # - # tabular: - # delims: 1 - # alignDoubleBackSlash: 1 - # - # or, simply, - # - # tabular: 1 - # - # We need to perform a check to see which has been done. - if(ref($lookForAlignDelims{$command}) eq 'HASH'){ - # tabular: - # delims: 1 - # alignDoubleBackSlash: 1 - $masterIndentationArrayOfHashes[-1]{alignmentDelimiters}=defined $lookForAlignDelims{$command}{delims}?$lookForAlignDelims{$command}{delims}:1; - $masterIndentationArrayOfHashes[-1]{alignDoubleBackSlash}=defined $lookForAlignDelims{$command}{alignDoubleBackSlash}?$lookForAlignDelims{$command}{alignDoubleBackSlash}:1; - $masterIndentationArrayOfHashes[-1]{spacesBeforeDoubleBackSlash}=$lookForAlignDelims{$command}{spacesBeforeDoubleBackSlash}||0; - } else { - # tabular: 1 - $masterIndentationArrayOfHashes[-1]{alignmentDelimiters}=1; - $masterIndentationArrayOfHashes[-1]{alignDoubleBackSlash}=1; - } - if($masterIndentationArrayOfHashes[-1]{alignmentDelimiters}==1){ - # tracing mode - print $logfile "Line $lineCounter\t Delimiter environment started: $command (see lookForAlignDelims)\n" if($tracingMode); - } - } - } else { - # commands, headings, etc - if(!$noAdditionalIndent{$command}){ - $masterIndentationArrayOfHashes[-1]{indent} = $indentRules{$command}||$defaultIndent; - } - } - - # details of noAdditionalIndent to the main hash - if($noAdditionalIndent{$command}){ - $masterIndentationArrayOfHashes[-1]{noAdditionalIndent} = 'yes'; - } -} - -sub decrease_indent{ - # PURPOSE: Adjust the indentation - # of the current environment; - # check that it's not an environment - # that doesn't want indentation. - - # if there is no evidence of indentation, then return - return unless(scalar(@masterIndentationArrayOfHashes)); - - # otherwise get details of the most recent command, environment, item, if, heading, etc - my $command = pop(@_); - - if(!$inverbatim) { - print $logfile "Line $lineCounter\t removing ", $masterIndentationArrayOfHashes[-1]{name}, " from masterIndentationArrayOfHashes\n" if($tracingMode); - pop(@masterIndentationArrayOfHashes); - # tracing mode - if($tracingMode) { - if(@masterIndentationArrayOfHashes) { - print $logfile "Line $lineCounter\t decreasing masterIndentationArrayOfHashes to: ",¤t_indentation_names(),"\n"; - } else { - print $logfile "Line $lineCounter\t masterIndentationArrayOfHashes now empty \n"; - } - } - } -} - -sub current_indentation{ - # PURPOSE: loop through masterIndentationArrayOfHashes and - # pull out the indentation, and join it together - - # if the masterIndentationArrayOfHashes is empty, return an empty string - return "" unless(@masterIndentationArrayOfHashes); - - my $indent; - foreach my $env (@masterIndentationArrayOfHashes){ - $indent .= defined($env->{indent})?$env->{indent}:''; - } - return $indent; -} - -sub current_indentation_names{ - # PURPOSE: loop through masterIndentationArrayOfHashes and - # pull out the list of environment/command names - return "masterIndentationArrayOfHashes empty" unless(@masterIndentationArrayOfHashes); - - my $listOfNames; - foreach my $env (@masterIndentationArrayOfHashes){ - $listOfNames .= $env->{name}; - $listOfNames .= "," unless $env == $masterIndentationArrayOfHashes[-1]; - } - return $listOfNames; -} - -sub check_conflicting_keys{ - # PURPOSE: users may sometimes put an environment in two - # hash keys; for example, they might put lstlistings - # in both indentRules and in noAdditionalIndent; - # in which case, we need a hierachy. - # - # This subroutine implements such a hierachy, - # and deletes the redundant key. - - # if the user has specified $indentRules{$command} and - # $noAdditionalIndent{$command} then they are a bit confused- - # we remove the $indentRules{$command} and assume that they - # want $noAdditionalIndent{$command} - - my $command = pop(@_); - - if(scalar($indentRules{$command}) and $noAdditionalIndent{$command}) { - print $logfile "WARNING\n\t Line $lineCounter\t $command is in *both* indentRules and noAdditionalIndent\n"; - print $logfile "\t\t\t ignoring indentRules and prioritizing noAdditionalIndent\n"; - print $logfile "\t\t\t Note that you only get this message once per command/environment\n"; - - # remove the key and value from %indentRules hash - # to avoid any further confusion - delete $indentRules{$command}; - } - - # if the command is in verbatimEnvironments and in indentRules then - # remove it from %indentRules hash - # to avoid any further confusion - if($indentRules{$command} and $verbatimEnvironments{$command}) { - # remove the key and value from %indentRules hash - # to avoid any further confusion - print $logfile "WARNING\n\t Line $lineCounter\t $command is in *both* indentRules and verbatimEnvironments\n"; - print $logfile "\t\t\t ignoring indentRules and prioritizing verbatimEnvironments\n"; - print $logfile "\t\t\t Note that you only get this message once per environment\n"; - delete $indentRules{$command}; - } - -} |