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 Data::Dumper; use Exporter qw/import/; use LatexIndent::Tokens qw/%tokens/; use LatexIndent::GetYamlSettings qw/%mainSettings/; use LatexIndent::Switches qw/$is_t_switch_active $is_tt_switch_active $is_m_switch_active/; use LatexIndent::LogFile qw/$logger/; our @EXPORT_OK = qw/put_verbatim_back_in find_verbatim_environments find_noindent_block find_verbatim_commands find_verbatim_special verbatim_common_tasks %verbatimStorage/; our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 our $verbatimCounter; our %verbatimStorage; sub find_noindent_block{ my $self = shift; # noindent block $logger->trace('*Searching for NOINDENTBLOCK (see noIndentBlock)') if $is_t_switch_active; $logger->trace(Dumper(\%{$mainSettings{noIndentBlock}})) if($is_tt_switch_active); while( my ($noIndentBlock,$yesno)= each %{$mainSettings{noIndentBlock}}){ # integrity check on the field for noIndentBlock if ( ref($yesno) eq "HASH" ){ if (defined ${$yesno}{lookForThis} and !${$yesno}{lookForThis}){ $logger->trace(" *not* looking for $noIndentBlock as lookForThis: 0") if $is_t_switch_active; next; } if (not defined ${$yesno}{name}) { if (not defined ${$yesno}{begin}){ $logger->trace(" *not* looking for $noIndentBlock as $noIndentBlock:begin not specified") if $is_t_switch_active; next; } elsif (not defined ${$yesno}{end}) { $logger->trace(" *not* looking for $noIndentBlock as $noIndentBlock:end not specified") if $is_t_switch_active; next; } } elsif (defined ${$yesno}{begin} or defined ${$yesno}{end}){ $logger->trace(" *not* looking for $noIndentBlock as $noIndentBlock:name specified with begin and/or end") if $is_t_switch_active; next; } } elsif( ref($yesno) ne "HASH" and !$yesno ){ $logger->trace(" *not* looking for $noIndentBlock as $noIndentBlock:$yesno") if $is_t_switch_active; next; } # if we've made it this far, then we're good to go my $noIndentRegExp; my $noIndentBlockObj; if (ref($yesno) eq "HASH"){ # default value of begin and end if (defined ${$yesno}{name} and not defined ${$yesno}{begin} and not defined ${$yesno}{end}){ ${$yesno}{begin} = "\\\\begin\\{(${$yesno}{name})\\}"; ${$yesno}{end} = "\\\\end\\{\\2\\}"; $logger->trace("looking for regex based $noIndentBlock, name: ${$yesno}{name}") if $is_t_switch_active; $logger->trace("begin not specified for $noIndentBlock, setting default ${$yesno}{begin}") if $is_t_switch_active; $logger->trace("end not specified for $noIndentBlock, setting default ${$yesno}{end}") if $is_t_switch_active; } # default value of body if (not defined ${$yesno}{body}){ $logger->trace("looking for regex based $noIndentBlock, begin: ${$yesno}{begin}, end: ${$yesno}{end}") if $is_t_switch_active; $logger->trace("body not specified for $noIndentBlock, setting default .*?") if $is_t_switch_active; ${$yesno}{body} = qr/.*?/sx; } else { $logger->trace("looking for regex based $noIndentBlock") if $is_t_switch_active; $logger->trace("begin: ${$yesno}{begin}") if $is_t_switch_active; $logger->trace("body: ${$yesno}{body}") if $is_t_switch_active; $logger->trace("end: ${$yesno}{end}") if $is_t_switch_active; } $noIndentRegExp = qr/ (${$yesno}{begin}) (${$yesno}{body}) (${$yesno}{end}) /sx; } else { $logger->trace("looking for $noIndentBlock:$yesno noIndentBlock") if $is_t_switch_active; (my $noIndentBlockSpec = $noIndentBlock) =~ s/\*/\\*/sg; $noIndentRegExp = qr/ ( (?!<\\) % (?:\h|(?!<\\)%)* # possible horizontal spaces \\begin\{ ($noIndentBlockSpec) # environment name captured into $2 \} # % \begin{noindentblock} statement ) # begin statement captured into $1 ( .*? # non-greedy match (body) into $3 ) ( (?!<\\) % # % (?:\h|(?!<\\)%)* # possible horizontal spaces \\end\{\2\} # % \end{noindentblock} statement ) # end statement captured into $4 /sx; } while( ${$self}{body} =~ m/$noIndentRegExp/sx){ # create a new Verbatim object if (ref($yesno) eq "HASH" and not defined ${$yesno}{name}){ # user defined begin and end statements $noIndentBlockObj = LatexIndent::Verbatim->new( begin=>$1, body=>$2, end=>$3, name=>$noIndentBlock, type=>"noindentblock", modifyLineBreaksYamlName=>"verbatim", ); } else { # specified by name (entry:1 or entry: name: regex) $noIndentBlockObj = LatexIndent::Verbatim->new( begin=>$1, body=>$3, end=>$4, name=>$2, type=>"noindentblock", modifyLineBreaksYamlName=>"verbatim", ); } # give unique id $noIndentBlockObj->create_unique_id; # verbatim children go in special hash $verbatimStorage{${$noIndentBlockObj}{id}}=$noIndentBlockObj; # log file output $logger->trace("NOINDENTBLOCK found: ${$noIndentBlockObj}{name}") if $is_t_switch_active; # remove the environment block, and replace with unique ID ${$self}{body} =~ s/$noIndentRegExp/${$noIndentBlockObj}{id}/sx; $logger->trace("replaced with ID: ${$noIndentBlockObj}{id}") if $is_t_switch_active; # possible decoration in log file $logger->trace(${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}) if ${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}; } } return; } sub find_verbatim_environments{ my $self = shift; # verbatim environments $logger->trace('*Searching for VERBATIM environments (see verbatimEnvironments)') if $is_t_switch_active; $logger->trace(Dumper(\%{$mainSettings{verbatimEnvironments}})) if($is_tt_switch_active); while( my ($verbEnv,$yesno)= each %{$mainSettings{verbatimEnvironments}}){ my $verbEnvSpec; # integrity check on the field for noIndentBlock if ( ref($yesno) eq "HASH" ){ if (defined ${$yesno}{lookForThis} and !${$yesno}{lookForThis}){ $logger->trace(" *not* looking for $verbEnv as lookForThis: 0") if $is_t_switch_active; next; } elsif (not defined ${$yesno}{name}){ $logger->trace(" *not* looking for $verbEnv as $verbEnv:name not specified") if $is_t_switch_active; next; } else { $logger->trace("looking for VERBATIM-environments $verbEnv, name: ${$yesno}{name}") if $is_t_switch_active; $verbEnvSpec = ${$yesno}{name}; } } elsif( ref($yesno) ne "HASH" and $yesno ){ $logger->trace("looking for $verbEnv:$yesno environments") if $is_t_switch_active; ($verbEnvSpec = $verbEnv) =~ s/\*/\\*/sg; } else { $logger->trace(" *not* looking for $verbEnv as $verbEnv:$yesno") if $is_t_switch_active; next; } # if we've made it this far, then we're good to go my $verbatimRegExp = qr/ ( \\begin\{ ($verbEnvSpec) # environment name captured into $2 \} # \begin{} statement captured into $1 ) ( .*? # non-greedy match (body) into $3 ) # any character, but not \\begin ( \\end\{\2\} # \end{} statement captured into $4 ) (\h*)? # possibly followed by horizontal space (\R)? # possibly followed by a line break /sx; while( ${$self}{body} =~ m/$verbatimRegExp/sx){ # create a new Verbatim object my $verbatimBlock = LatexIndent::Verbatim->new( begin=>$1, body=>$3, end=>$4, name=>$2, type=>"environment", modifyLineBreaksYamlName=>"verbatim", linebreaksAtEnd=>{ end=>$6?1:0, }, horizontalTrailingSpace=>$5?$5:q(), aliases=>{ # begin statements BeginStartsOnOwnLine=>"VerbatimBeginStartsOnOwnLine", # after end statements EndFinishesWithLineBreak=>"VerbatimEndFinishesWithLineBreak", }, ); # there are common tasks for each of the verbatim objects $verbatimBlock->verbatim_common_tasks; # verbatim children go in special hash $verbatimStorage{${$verbatimBlock}{id}}=$verbatimBlock; # log file output $logger->trace("*VERBATIM environment found: ${$verbatimBlock}{name}") if $is_t_switch_active; # remove the environment block, and replace with unique ID ${$self}{body} =~ s/$verbatimRegExp/${$verbatimBlock}{replacementText}/sx; $logger->trace("replaced with ID: ${$verbatimBlock}{id}") if $is_t_switch_active; # possible decoration in log file $logger->trace(${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}) if ${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}; } } 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 $logger->trace('*Searching for VERBATIM commands (see verbatimCommands)') if $is_t_switch_active; $logger->trace(Dumper(\%{$mainSettings{verbatimCommands}})) if($is_tt_switch_active); while( my ($verbCommand,$yesno)= each %{$mainSettings{verbatimCommands}}){ my $verbCommandSpec; # integrity check on the field for noIndentBlock if ( ref($yesno) eq "HASH" ){ if (defined ${$yesno}{lookForThis} and !${$yesno}{lookForThis}){ $logger->trace(" *not* looking for $verbCommand as lookForThis: 0") if $is_t_switch_active; next; } elsif (not defined ${$yesno}{name}){ $logger->trace(" *not* looking for $verbCommand as $verbCommand:name not specified") if $is_t_switch_active; next; } else { $logger->trace("looking for regex based VERBATIM-commands $verbCommand, name: ${$yesno}{name}") if $is_t_switch_active; $verbCommandSpec = ${$yesno}{name}; } } elsif( ref($yesno) ne "HASH" and $yesno ){ $logger->trace("looking for $verbCommand:$yesno Commands") if $is_t_switch_active; $verbCommandSpec = $verbCommand; } else { $logger->trace("*not* looking for $verbCommand as $verbCommand:$yesno") if $is_t_switch_active; next; } # if we've made it this far, then we're good to go my $verbatimRegExp = qr/ ( \\($verbCommandSpec) # name of command into $2 \h* ) ( \[ (?: (?! (?:(?new( begin=>$1.($3?$3:q()).$4, body=>$5, end=>$4, name=>$2, type=>"command", modifyLineBreaksYamlName=>"verbatim", linebreaksAtEnd=>{ end=>$7?1:0, }, horizontalTrailingSpace=>$6?$6:q(), aliases=>{ # begin statements BeginStartsOnOwnLine=>"VerbatimBeginStartsOnOwnLine", # after end statements EndFinishesWithLineBreak=>"VerbatimEndFinishesWithLineBreak", }, optArg=>$3?$3:q(), ); # there are common tasks for each of the verbatim objects $verbatimCommand->verbatim_common_tasks; # output, if desired $logger->trace(Dumper($verbatimCommand),'ttrace') if($is_tt_switch_active); # verbatim children go in special hash $verbatimStorage{${$verbatimCommand}{id}}=$verbatimCommand; # log file output $logger->trace("*VERBATIM command found: ${$verbatimCommand}{name}") if $is_t_switch_active; # remove the environment block, and replace with unique ID ${$self}{body} =~ s/$verbatimRegExp/${$verbatimCommand}{replacementText}/sx; $logger->trace("replaced with ID: ${$verbatimCommand}{id}") if $is_t_switch_active; # possible decoration in log file $logger->trace(${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}) if ${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}; } } return; } sub find_verbatim_special{ my $self = shift; # loop through specialBeginEnd while( my ($specialName,$BeginEnd)= each %{$mainSettings{specialBeginEnd}}){ # only classify special Verbatim if lookForThis is 'verbatim' if( (ref($BeginEnd) eq "HASH") and ${$BeginEnd}{lookForThis}=~m/v/s and ${$BeginEnd}{lookForThis} eq 'verbatim'){ $logger->trace('*Searching for VERBATIM special (see specialBeginEnd)') if $is_t_switch_active; my $verbatimRegExp = qr/ ( ${$BeginEnd}{begin} ) ( .*? ) ( ${$BeginEnd}{end} ) (\h*)? # possibly followed by horizontal space (\R)? # possibly followed by a line break /sx; while( ${$self}{body} =~ m/$verbatimRegExp/sx){ # create a new Verbatim object my $verbatimBlock = LatexIndent::Verbatim->new( begin=>$1, body=>$2, end=>$3, name=>$specialName, modifyLineBreaksYamlName=>"specialBeginEnd", linebreaksAtEnd=>{ end=>$5?1:0, }, horizontalTrailingSpace=>$4?$4:q(), type=>"special", aliases=>{ # begin statements BeginStartsOnOwnLine=>"SpecialBeginStartsOnOwnLine", # after end statements EndFinishesWithLineBreak=>"SpecialEndFinishesWithLineBreak", }, ); # there are common tasks for each of the verbatim objects $verbatimBlock->verbatim_common_tasks; # verbatim children go in special hash $verbatimStorage{${$verbatimBlock}{id}}=$verbatimBlock; # log file output $logger->trace("*VERBATIM special found: $specialName") if $is_t_switch_active; # remove the special block, and replace with unique ID ${$self}{body} =~ s/$verbatimRegExp/${$verbatimBlock}{replacementText}/sx; $logger->trace("replaced with ID: ${$verbatimBlock}{id}") if $is_t_switch_active; # possible decoration in log file $logger->trace(${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}) if ${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}; } } } } sub put_verbatim_back_in { my $self = shift; my %input = @_; my $verbatimCount=0; my $toMatch = q(); if($input{match} eq "everything-except-commands"){ $toMatch = "noindentblockenvironmentspeciallinesprotect"; } else { $toMatch = "command"; } # count the number of non-command verbatim objects while( my ($key,$child)= each %verbatimStorage){ ${$child}{type} = "environment" if !(defined ${$child}{type}); $verbatimCount++ if($toMatch =~ m/${$child}{type}/); } return unless($verbatimCount>0); # search for environments/commands $logger->trace('*Putting verbatim back in') if $is_t_switch_active; $logger->trace('pre-processed body:') if $is_tt_switch_active; $logger->trace(${$self}{body}) if($is_tt_switch_active); # loop through document children hash my $verbatimFound=0; while($verbatimFound < $verbatimCount){ while( my ($verbatimID,$child)= each %verbatimStorage){ if($toMatch =~ m/${$child}{type}/){ if(${$self}{body} =~ m/$verbatimID/m){ # possibly remove trailing line break if($is_m_switch_active and defined ${$child}{EndFinishesWithLineBreak} and ${$child}{EndFinishesWithLineBreak}==-1 and ${$self}{body} =~ m/$verbatimID\h*\R/s){ $logger->trace("m-switch active, removing trailing line breaks from ${$child}{name}") if $is_t_switch_active; ${$self}{body} =~ s/$verbatimID(\h*)?(\R|\h)*/$verbatimID /s; } # line protection mode can allow line breaks to be removed # at end of verbatim; these need to be added back in # # see # # test-cases/line-switch-test-cases/environments-simple-nested-mod13.tex # if(${$child}{type} eq "linesprotect"){ # remove leading space ahead of verbatim ID ${$self}{body} =~ s/^\h*$verbatimID/$verbatimID/m; if($is_m_switch_active and ${$self}{body}=~ m/$verbatimID\h*\S/s){ ${$self}{body} =~ s/$verbatimID\h*(\S)/$verbatimID\n$1/s; } } # replace ids with body ${$self}{body} =~ s/$verbatimID/${$child}{begin}${$child}{body}${$child}{end}/s; # log file info $logger->trace('Body now looks like:') if $is_tt_switch_active; $logger->trace(${$self}{body},'ttrace') if($is_tt_switch_active); # delete the child so it won't be operated upon again delete $verbatimStorage{$verbatimID}; $verbatimFound++; } elsif ($is_m_switch_active and ${$mainSettings{modifyLineBreaks}{textWrapOptions}}{columns}>1 and ${$mainSettings{modifyLineBreaks}{textWrapOptions}}{huge} ne "overflow" and ${$self}{body} !~ m/${$child}{id}/){ $logger->trace("$verbatimID not found in body using /m matching, it may have been split across line (see modifyLineBreaks: textWrapOptions)") if($is_t_switch_active); # search for a version of the verbatim ID that may have line breaks my $verbatimIDwithLineBreaks = join("\\R?",split(//,$verbatimID)); my $verbatimIDwithLineBreaksRegExp = qr/$verbatimIDwithLineBreaks/s; # replace the line-broken verbatim ID with a non-broken verbatim ID ${$self}{body} =~ s/$verbatimIDwithLineBreaksRegExp/${$child}{id}/s; # note: we do *not* label this as found, as we need to go back through # and search for the newly modified ID } } # logfile info $logger->trace('*Post-processed body:') if $is_tt_switch_active; $logger->trace(${$self}{body}) if($is_tt_switch_active); } } return; } sub verbatim_common_tasks{ my $self = shift; # get yaml settings $self->yaml_modify_line_breaks_settings if $is_m_switch_active; # give unique id $self->create_unique_id; # 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 end statements $self->modify_line_breaks_end if ($is_m_switch_active and defined ${$self}{EndStartsOnOwnLine} and ${$self}{EndStartsOnOwnLine}!=0); $self->modify_line_breaks_end_after if ($is_m_switch_active and defined ${$self}{EndFinishesWithLineBreak} and ${$self}{EndFinishesWithLineBreak}!=0); } sub create_unique_id{ my $self = shift; $verbatimCounter++; ${$self}{id} = "$tokens{verbatim}$verbatimCounter$tokens{endOfToken}"; return; } sub yaml_get_textwrap_removeparagraphline_breaks{ my $self = shift; $logger->trace("No text wrap or remove paragraph line breaks for verbatim code blocks, ${$self}{name}") if ${$mainSettings{logFilePreferences}}{showDecorationFinishCodeBlockTrace}; } 1;