File Coverage

bin/eshu
Criterion Covered Total %
statement 103 154 66.8
branch 45 110 40.9
condition 13 32 40.6
subroutine 7 9 77.7
pod n/a
total 168 305 55.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2 10     10   38352 use strict;
  10         15  
  10         332  
3 10     10   52 use warnings;
  10         12  
  10         512  
4 10     10   6122 use Getopt::Long qw(:config no_ignore_case bundling);
  10         122084  
  10         49  
5 10     10   5425 use Eshu;
  10         24  
  10         25295  
6              
7 10         1236980 my $fix = 0;
8 10         20 my $diff = 0;
9 10         19 my $check = 0;
10 10         20 my $lang;
11 10         29 my $tabs = 0;
12 10         19 my $spaces;
13 10         16 my $indent_pp = 0;
14 10         13 my $no_indent_pp = 0;
15 10         28 my $range;
16             my @exclude;
17 10         0 my @include;
18 10         19 my $verbose = 0;
19              
20 10 50       128 GetOptions(
21             'fix' => \$fix,
22             'diff' => \$diff,
23             'check' => \$check,
24             'lang=s' => \$lang,
25             'tabs' => \$tabs,
26             'spaces=i' => \$spaces,
27             'indent-pp' => \$indent_pp,
28             'no-indent-pp' => \$no_indent_pp,
29             'range=s' => \$range,
30             'exclude=s' => \@exclude,
31             'include=s' => \@include,
32             'verbose|v' => \$verbose,
33             ) or usage();
34              
35             sub usage {
36 0     0   0 print STDERR <<'USAGE';
37             Usage: eshu [OPTIONS] [FILE|DIR ...]
38              
39             Options:
40             --fix Edit file(s) in-place
41             --diff Show unified diff of changes (no write)
42             --check Exit 1 if file(s) would change (for CI)
43             --lang Override language detection
44             --tabs Use tabs for indentation (default)
45             --spaces Use N spaces per indent level
46             --indent-pp Indent C preprocessor directives
47             --no-indent-pp Leave preprocessor at column 0 (default)
48             --range START,END Only reindent lines START through END (1-based)
49             --exclude Skip files matching regex pattern (repeatable)
50             --include Only process files matching regex pattern (repeatable)
51             --verbose, -v Print each file processed
52              
53             If no FILE is given, reads from stdin and writes to stdout.
54             If a directory is given, processes all recognised files recursively.
55             USAGE
56 0         0 exit 2;
57             }
58              
59 10         12095 my %opts;
60 10 100       89 if (defined $spaces) {
    100          
61 1         9 $opts{indent_char} = ' ';
62 1         2 $opts{indent_width} = $spaces;
63             } elsif ($tabs) {
64 1         3 $opts{indent_char} = "\t";
65 1         1 $opts{indent_width} = 1;
66             }
67 10 50       31 $opts{indent_pp} = 1 if $indent_pp;
68 10 50       33 $opts{indent_pp} = 0 if $no_indent_pp;
69 10 50       27 if (defined $range) {
70 0 0       0 if ($range =~ /^(\d+),(\d+)$/) {
71 0         0 $opts{range_start} = $1;
72 0         0 $opts{range_end} = $2;
73             } else {
74 0         0 die "eshu: --range must be START,END (e.g. --range 10,25)\n";
75             }
76             }
77              
78 10         46 my $exit_code = 0;
79              
80 10 100       28 if (@ARGV) {
81 7         15 for my $arg (@ARGV) {
82 7 50       235 if (-d $arg) {
83 0         0 process_dir($arg);
84             } else {
85 7         32 process_file($arg);
86             }
87             }
88             } else {
89 3         12 process_stdin();
90             }
91              
92 10         0 exit $exit_code;
93              
94             sub process_dir {
95 0     0   0 my ($dir) = @_;
96              
97 0         0 my %dir_opts;
98 0 0       0 $dir_opts{lang} = $lang if defined $lang;
99 0 0       0 $dir_opts{fix} = 1 if $fix;
100 0 0       0 $dir_opts{diff} = 1 if $diff;
101 0 0       0 if (defined $spaces) {
    0          
102 0         0 $dir_opts{indent_char} = ' ';
103 0         0 $dir_opts{indent_width} = $spaces;
104             } elsif ($tabs) {
105 0         0 $dir_opts{indent_char} = "\t";
106 0         0 $dir_opts{indent_width} = 1;
107             }
108 0 0       0 $dir_opts{indent_pp} = 1 if $indent_pp;
109 0 0       0 $dir_opts{indent_pp} = 0 if $no_indent_pp;
110 0 0       0 if (@exclude) {
111 0         0 $dir_opts{exclude} = [ map { qr/$_/ } @exclude ];
  0         0  
112             }
113 0 0       0 if (@include) {
114 0         0 $dir_opts{include} = [ map { qr/$_/ } @include ];
  0         0  
115             }
116              
117 0         0 my $report = Eshu->indent_dir($dir, %dir_opts);
118              
119 0 0       0 if ($check) {
    0          
120 0         0 for my $c (@{$report->{changes}}) {
  0         0  
121 0 0       0 if ($c->{status} eq 'needs_fixing') {
122 0         0 print STDERR "$c->{file}\n";
123 0         0 $exit_code = 1;
124             }
125             }
126             } elsif ($diff) {
127 0         0 for my $c (@{$report->{changes}}) {
  0         0  
128 0 0       0 if ($c->{diff}) {
129 0         0 print $c->{diff};
130             }
131             }
132             }
133              
134 0 0 0     0 if ($verbose || (!$check && !$diff && !$fix)) {
      0        
135 0         0 for my $c (@{$report->{changes}}) {
  0         0  
136 0 0       0 my $lang_str = $c->{lang} ? "[$c->{lang}]" : '';
137 0 0       0 if ($c->{status} eq 'skipped') {
    0          
    0          
    0          
    0          
138 0 0 0     0 printf "%s: skipped (%s)\n", $c->{file}, $c->{reason} || '?'
139             if $verbose;
140             } elsif ($c->{status} eq 'error') {
141 0   0     0 printf "%s: error (%s)\n", $c->{file}, $c->{error} || '?';
142             } elsif ($c->{status} eq 'changed') {
143 0         0 printf "%s: %s fixed\n", $c->{file}, $lang_str;
144             } elsif ($c->{status} eq 'needs_fixing') {
145 0         0 printf "%s: %s needs fixing\n", $c->{file}, $lang_str;
146             } elsif ($c->{status} eq 'unchanged') {
147 0 0       0 printf "%s: %s ok\n", $c->{file}, $lang_str if $verbose;
148             }
149             }
150             printf "---\n%d files checked, %d changed, %d skipped, %d errors\n",
151             $report->{files_checked}, $report->{files_changed},
152             $report->{files_skipped}, $report->{files_errored}
153 0 0 0     0 if $verbose || (!$check && !$diff && !$fix);
      0        
154             }
155             }
156              
157             sub process_file {
158 7     7   18 my ($file) = @_;
159              
160 7 50       347 open my $fh, '<', $file or die "eshu: cannot open '$file': $!\n";
161 7         26 my $src = do { local $/; <$fh> };
  7         35  
  7         272  
162 7         82 close $fh;
163              
164 7   50     268 my $detected = $lang || Eshu->detect_lang($file) || 'c';
165 7         87 my $result = Eshu->indent_string($src, lang => $detected, %opts);
166              
167 7 100       26 if ($check) {
168 3 100       11 if ($result ne $src) {
169 2         56 print STDERR "$file\n";
170 2         5 $exit_code = 1;
171             }
172 3         19 return;
173             }
174              
175 4 100       14 if ($diff) {
176 2 100       6 if ($result ne $src) {
177 1         3 print_diff($file, $src, $result);
178             }
179 2         9 return;
180             }
181              
182 2 100       6 if ($fix) {
183 1 50       3 if ($result ne $src) {
184 1 50       84 open my $out, '>', $file or die "eshu: cannot write '$file': $!\n";
185 1         4 print $out $result;
186 1         145 close $out;
187             }
188 1         6 return;
189             }
190              
191             # Default: write to stdout
192 1         10 print $result;
193             }
194              
195             sub process_stdin {
196 3   50 3   8 my $detected = $lang || 'c';
197 3         3 my $src = do { local $/; };
  3         11  
  3         131  
198 3         42 my $result = Eshu->indent_string($src, lang => $detected, %opts);
199              
200 3 50       9 if ($check) {
201 0 0       0 $exit_code = 1 if $result ne $src;
202 0         0 return;
203             }
204              
205 3 50       7 if ($diff) {
206 0 0       0 print_diff('stdin', $src, $result) if $result ne $src;
207 0         0 return;
208             }
209              
210 3         9 print $result;
211             }
212              
213             sub print_diff {
214 1     1   3 my ($label, $old, $new) = @_;
215 1         4 my @old_lines = split /^/m, $old;
216 1         2 my @new_lines = split /^/m, $new;
217              
218 1         4 print "--- a/$label\n";
219 1         2 print "+++ b/$label\n";
220              
221             # Simple line-by-line diff
222 1 50       3 my $max = @old_lines > @new_lines ? scalar @old_lines : scalar @new_lines;
223 1         1 my $hunk_start = -1;
224 1         1 my @hunk_old;
225             my @hunk_new;
226              
227 1         3 for (my $i = 0; $i <= $max; $i++) {
228 4 100       6 my $ol = $i < @old_lines ? $old_lines[$i] : undef;
229 4 100       6 my $nl = $i < @new_lines ? $new_lines[$i] : undef;
230 4   100     12 my $same = defined $ol && defined $nl && $ol eq $nl;
231              
232 4 100 66     10 if (!$same && $hunk_start < 0) {
233 2         2 $hunk_start = $i;
234 2         2 @hunk_old = ();
235 2         3 @hunk_new = ();
236             }
237              
238 4 100 100     7 if ($hunk_start >= 0 && !$same) {
239 2 100       4 push @hunk_old, $ol if defined $ol;
240 2 100       5 push @hunk_new, $nl if defined $nl;
241             }
242              
243 4 100 100     10 if ($same || $i == $max) {
244 3 100       6 if ($hunk_start >= 0) {
245 2         6 my $old_count = scalar @hunk_old;
246 2         2 my $new_count = scalar @hunk_new;
247 2         7 printf "@@ -%d,%d +%d,%d @@\n",
248             $hunk_start + 1, $old_count,
249             $hunk_start + 1, $new_count;
250 2         2 for my $l (@hunk_old) {
251 1 50       5 $l = "$l\n" unless $l =~ /\n$/;
252 1         3 print "-$l";
253             }
254 2         3 for my $l (@hunk_new) {
255 1 50       3 $l = "$l\n" unless $l =~ /\n$/;
256 1         1 print "+$l";
257             }
258 2         4 $hunk_start = -1;
259             }
260             }
261             }
262             }