package LatexIndent::Lines; # 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::LogFile qw/$logger/; use LatexIndent::Switches qw/%switches/; use LatexIndent::Verbatim qw/%verbatimStorage/; our @ISA = "LatexIndent::Document"; # class inheritance, Programming Perl, pg 321 our @EXPORT_OK = qw/lines_body_selected_lines lines_verbatim_create_line_block/; sub lines_body_selected_lines { my $self = shift; my @lines = @{$_[0]}; # strip all space from lines switch $switches{lines} =~ s/\h//sg; $logger->info("*-n,--lines switch is active, operating on lines $switches{lines}"); $logger->info("number of lines in file: ".($#lines+1)); $logger->info("*interpretting $switches{lines}"); my @lineRanges = split(/,/,$switches{lines}); my @indentLineRange; my @NOTindentLineRange; my %minMaxStorage; my %negationMinMaxStorage; # loop through line ranges, which are separated by commas # # --lines 3-15,17-19 # foreach(@lineRanges){ my $minLine = 0; my $maxLine = 0; my $negationMode = 0; # # --lines !3-15 # if($_ =~ m/!/s){ $negationMode = 1; $_ =~ s/!//s; $logger->info("negation mode active as $_"); } # --lines min-max if($_ =~ m/-/s){ ($minLine,$maxLine) = split(/-/,$_); } else { $minLine = $_; $maxLine = $_; } # both minLine and maxLine need to be INTEGERS if ($minLine !~ m/^\d+$/ or $maxLine !~ m/^\d+$/){ $logger->warn("*$_ not a valid line specification; I'm ignoring this entry"); next; } # swap minLine and maxLine if necessary if($minLine > $maxLine){ ($minLine,$maxLine) = ($maxLine, $minLine); } # minline > number of lines needs addressing if ($minLine-1 > $#lines){ $logger->warn("*--lines specified with min line $minLine which is *greater than* the number of lines in file: ".($#lines+1)); $logger->warn("adjusting this value to be ".($#lines+1)); $minLine = $#lines+1; } # maxline > number of lines needs addressing if ($maxLine-1 > $#lines){ $logger->warn("*--lines specified with max line $maxLine which is *greater than* the number of lines in file: ".($#lines+1)); $logger->warn("adjusting this value to be ".($#lines+1)); $maxLine = $#lines+1; } # either store the negation, or not if($negationMode){ $negationMinMaxStorage{$minLine} = $maxLine; } else { $minMaxStorage{$minLine} = $maxLine; } $logger->info("min line: $minLine, max line: $maxLine"); } # only proceed if we have a valid line range if ( (keys %minMaxStorage) < 1 and (keys %negationMinMaxStorage) < 1){ $logger->warn("*--lines not specified with valid range: $switches{lines}"); $logger->warn("entire body will be indented, and ignoring $switches{lines}"); $switches{lines} = 0; ${$self}{body} = join("",@lines); return; } # we need to perform the token check here ${$self}{body} = join("",@lines); $self->token_check; ${$self}{body} = q(); # negated line ranges if (keys %negationMinMaxStorage >= 1){ @NOTindentLineRange = &lines_sort_and_combine_line_range(\%negationMinMaxStorage); $logger->info("*negation line range summary: "); $logger->info("the number of NEGATION line ranges: ".(($#NOTindentLineRange+1)/2)); $logger->info("the *sorted* NEGATION line ranges are in the form MIN-MAX: "); for (my $index=0; $index<(($#NOTindentLineRange+1)/2); $index++){ $logger->info(join("-",@NOTindentLineRange[2*$index..2*$index+1])); if($index==0 and $NOTindentLineRange[2*$index]>1){ $minMaxStorage{1} = $NOTindentLineRange[2*$index]-1; } elsif($index>0){ $minMaxStorage{$NOTindentLineRange[2*$index-1]+1} = $NOTindentLineRange[2*$index]-1; } } # final range if($NOTindentLineRange[-1]<$#lines){ $minMaxStorage{$NOTindentLineRange[-1]+1} = $#lines+1; } } @indentLineRange = &lines_sort_and_combine_line_range(\%minMaxStorage) if (keys %minMaxStorage >= 1) ; $logger->info("*line range summary: "); $logger->info("the number of indent line ranges: ".(($#indentLineRange+1)/2)); $logger->info("the *sorted* line ranges are in the form MIN-MAX: "); for (my $index=0; $index<(($#indentLineRange+1)/2); $index++){ $logger->info(join("-",@indentLineRange[2*$index..2*$index+1])); } my $startLine = 0; # now that we have the line range, we can sort arrange the body while ($#indentLineRange>0){ my $minLine = shift(@indentLineRange); my $maxLine = shift(@indentLineRange); # perl arrays start at 0 $minLine--; $maxLine--; $self->lines_verbatim_create_line_block(\@lines,$startLine,$minLine-1) unless ($minLine == 0); ${$self}{body} .= join("",@lines[$minLine..$maxLine]); $startLine = $maxLine+1; } # final line range $self->lines_verbatim_create_line_block(\@lines,$startLine,$#lines) if($startLine<=$#lines); return; } sub lines_sort_and_combine_line_range{ my %minMaxStorage = %{$_[0]}; # # --lines 8-10,4-5,1-2 # # needs to be interpretted as # # --lines 1-2,4-5,8-10, # # sort the line ranges by the *minimum* value, the associated # maximum values will be arranged after this my @indentLineRange = sort { $a <=> $b } keys(%minMaxStorage); my @justMinimumValues = @indentLineRange; for (my $index=0;$index<=$#justMinimumValues;$index++){ splice(@indentLineRange,2*$index+1,0,$minMaxStorage{$justMinimumValues[$index]}); } for (my $index=1;$index<(($#indentLineRange+1)/2);$index++){ my $currentMin = $indentLineRange[2*$index]; my $currentMax = $indentLineRange[2*$index+1]; my $previousMax = $indentLineRange[2*$index-1]; my $previousMin = $indentLineRange[2*$index-2]; if ( ($currentMin - 1) <= $previousMax and ( $currentMax > $previousMax ) ){ # overlapping line ranges, for example # # --lines 3-5,4-10 # # needs to be interpretted as # # --lines 3-10 # $logger->info("overlapping line range found"); $logger->info("line ranges (before): ".join(", ",@indentLineRange)); splice(@indentLineRange,2*$index-1,2); $logger->info("line ranges (after): ".join(", ",@indentLineRange)); # reset index so that loop starts again $index = 0; } elsif ( ($currentMin - 1) <= $previousMax and ( $currentMax <= $previousMax ) ){ # overlapping line ranges, for example # # --lines 3-7,4-6 # # needs to be interpretted as # # --lines 3-7 # $logger->info("overlapping line range found"); $logger->info("line ranges (before): ".join(", ",@indentLineRange)); splice(@indentLineRange,2*$index,2); $logger->info("line ranges (after): ".join(", ",@indentLineRange)); # reset index so that loop starts again $index = 0; } } return @indentLineRange; } sub lines_verbatim_create_line_block{ my $self = shift; my @lines = @{$_[0]}; my $startLine = $_[1]; my $finishLine = $_[2]; my $verbBody = join("",@lines[$startLine..$finishLine]); $verbBody =~ s/\R?$//s; # create a new Verbatim object my $noIndentBlockObj = LatexIndent::Verbatim->new( begin=>q(), body=>$verbBody, end=>q(), name=>"line-switch-verbatim-protection", type=>"linesprotect", modifyLineBreaksYamlName=>"lines-not-to-be-indented", ); # give unique id $noIndentBlockObj->create_unique_id; # verbatim children go in special hash $verbatimStorage{${$noIndentBlockObj}{id}}=$noIndentBlockObj; # remove the environment block, and replace with unique ID ${$self}{body} .= ${$noIndentBlockObj}{id}."\n"; return; } 1;