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/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/LatexIndent')
32 files changed, 5614 insertions, 0 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; |