# $NetBSD: FileUtil.pm,v 1.1 2015/11/25 16:42:21 rillig Exp $ # # Subroutines for loading and saving line-oriented files. # The load_file() subroutine loads a file completely into memory, # optionally handling continuation line folding. The load_lines() subrou- # tine is an abbreviation for the common case of loading files without # continuation lines. The save_autofix_changes() subroutine examines an # array of lines if some of them have changed. It then saves the modified # files. # package PkgLint::FileUtil; use strict; use warnings; BEGIN { use Exporter; use vars qw(@ISA @EXPORT_OK); @ISA = qw(Exporter); @EXPORT_OK = qw( load_file load_lines save_autofix_changes ); import PkgLint::Util qw( false true ); import PkgLint::Logging qw( NO_LINE_NUMBER log_error log_note ); } sub load_physical_lines($) { my ($fname) = @_; my ($physlines, $line, $lineno); $physlines = []; open(my $f, "<", $fname) or return; $lineno = 0; while (defined($line = <$f>)) { $lineno++; push(@{$physlines}, [$lineno, $line]); } close($f) or return; return $physlines; } sub get_logical_line($$$) { my ($fname, $lines, $ref_lineno) = @_; my ($value, $lineno, $first, $firstlineno, $lastlineno, $physlines); $value = ""; $first = true; $lineno = ${$ref_lineno}; $firstlineno = $lines->[$lineno]->[0]; $physlines = []; for (; $lineno <= $#{$lines}; $lineno++) { if ($lines->[$lineno]->[1] =~ m"^([ \t]*)(.*?)([ \t]*)(\\?)\n?$") { my ($indent, $text, $outdent, $cont) = ($1, $2, $3, $4); if ($first) { $value .= $indent; $first = false; } $value .= $text; push(@{$physlines}, $lines->[$lineno]); if ($cont eq "\\") { $value .= " "; } else { $value .= $outdent; last; } } } if ($lineno > $#{$lines}) { # The last line in the file is a continuation line $lineno--; } $lastlineno = $lines->[$lineno]->[0]; ${$ref_lineno} = $lineno + 1; return PkgLint::Line->new($fname, $firstlineno == $lastlineno ? $firstlineno : "$firstlineno--$lastlineno", $value, $physlines); } sub load_lines($$) { my ($fname, $fold_backslash_lines) = @_; my ($physlines, $seen_newline, $loglines); $physlines = load_physical_lines($fname); if (!$physlines) { return false; } $seen_newline = true; $loglines = []; if ($fold_backslash_lines) { for (my $lineno = 0; $lineno <= $#{$physlines}; ) { push(@{$loglines}, get_logical_line($fname, $physlines, \$lineno)); } } else { foreach my $physline (@{$physlines}) { my $text = $physline->[1]; $text =~ s/\n$//; push(@{$loglines}, PkgLint::Line->new($fname, $physline->[0], $text, [$physline])); } } if (0 <= $#{$physlines} && $physlines->[-1]->[1] !~ m"\n$") { log_error($fname, $physlines->[-1]->[0], "File must end with a newline."); } return $loglines; } sub load_file($) { my ($fname) = @_; return load_lines($fname, false); } sub save_autofix_changes($) { my ($lines) = @_; my (%changed, %physlines); foreach my $line (@{$lines}) { if ($line->is_changed) { $changed{$line->fname}++; } push(@{$physlines{$line->fname}}, @{$line->physlines}); } foreach my $fname (sort(keys(%changed))) { my $new = "${fname}.pkglint.tmp"; my $f; if (!open($f, ">", $new)) { log_error($new, NO_LINE_NUMBER, "$!"); next; } foreach my $physline (@{$physlines{$fname}}) { print $f ($physline->[1]); } if (!close($f)) { log_error($new, NO_LINE_NUMBER, "$!"); next; } if (!rename($new, $fname)) { log_error($fname, NO_LINE_NUMBER, "$!"); next; } log_note($fname, NO_LINE_NUMBER, "Has been autofixed. Please re-run pkglint."); } }