File Coverage

blib/lib/Syntax/Highlight/Shell.pm
Criterion Covered Total %
statement 38 42 90.4
branch 10 14 71.4
condition 1 3 33.3
subroutine 6 6 100.0
pod 2 2 100.0
total 57 67 85.0


line stmt bran cond sub pod time code
1             package Syntax::Highlight::Shell;
2 3     3   88143 use strict;
  3         10  
  3         164  
3 3     3   29606 use Shell::Parser;
  3         27001  
  3         145  
4              
5 3     3   40 { no strict;
  3         12  
  3         2830  
6               $VERSION = '0.04';
7               @ISA = qw(Shell::Parser);
8             }
9              
10             =head1 NAME
11            
12             Syntax::Highlight::Shell - Highlight shell scripts
13            
14             =head1 VERSION
15            
16             Version 0.04
17            
18             =cut
19              
20             my %classes = (
21                 metachar => 's-mta', # shell metacharacters (; |, >, &, \)
22                 keyword => 's-key', # a shell keyword (if, for, while, do...)
23                 builtin => 's-blt', # a builtin command
24                 command => 's-cmd', # an external command
25                 argument => 's-arg', # command arguments
26                 quote => 's-quo', # single (') and double (") quotes
27                 variable => 's-var', # an expanded variable ($VARIABLE)
28                 assigned => 's-avr', # an assigned variable (VARIABLE=value)
29                 value => 's-val', # a value
30                 comment => 's-cmt', # a comment
31                 line_number => 's-lno', # line number
32             );
33              
34             my %defaults = (
35                 pre => 1, # add <pre>...</pre> around the result? (default: yes)
36                 nnn => 0, # add line numbers (default: no)
37                 syntax => 'bourne', # shell syntax (default: Bourne shell)
38                 tabs => 4, # convert tabs to this number of spaces; zero to disable
39             );
40              
41             =head1 SYNOPSIS
42            
43             use Syntax::Highlight::Shell;
44            
45             my $highlighter = new Syntax::Highlight::Shell;
46             $output = $highlighter->parse($script);
47            
48             If C<$script> contains the following shell code:
49            
50             # an if statement
51             if [ -f /etc/passwd ]; then
52             grep $USER /etc/passwd | awk -F: '{print $5}' /etc/passwd
53             fi
54            
55             then the resulting HTML contained in C<$output> will render like this:
56            
57             =begin html
58            
59             <style type="text/css">
60             <!--
61             .s-mta, /* shell metacharacters */
62             .s-quo, /* quotes */
63             .s-key, /* shell keywords */
64             .s-blt { color: #993333; font-weight: bold; } /* shell builtins commands */
65             .s-var { color: #6633cc; } /* expanded variables */
66             .s-avr { color: #339999; } /* assigned variables */
67             .s-val { color: #cc3399; } /* values inside quotes */
68             .s-cmt { color: #338833; font-style: italic; } /* comment */
69             .s-lno { color: #aaaaaa; background: #f7f7f7;} /* line numbers */
70             -->
71             </style>
72            
73             <pre>
74             <span class="s-cmt"># an if statement</span>
75             <span class="s-key">if</span> [ -f /etc/passwd ]<span class="s-mta">;</span> <span class="s-key">then</span>
76             grep <span class="s-var">$USER</span> /etc/passwd <span class="s-mta">|</span> awk -F: <span class="s-quo">'</span><span class="s-val">{print $5}</span><span class="s-quo">'</span> /etc/passwd
77             <span class="s-key">fi</span>
78             </pre>
79            
80             =end html
81            
82             =head1 DESCRIPTION
83            
84             This module is designed to take shell scripts and highlight them in HTML
85             with meaningful colours using CSS. The resulting HTML output is ready for
86             inclusion in a web page. Note that no reformating is done, all spaces are
87             preserved.
88            
89             =head1 METHODS
90            
91             =over 4
92            
93             =item new()
94            
95             The constructor. Returns a C<Syntax::Highlight::Shell> object, which derives
96             from C<Shell::Parser>.
97            
98             B<Options>
99            
100             =over 4
101            
102             =item *
103            
104             C<nnn> - Activate line numbering. Default value: 0 (disabled).
105            
106             =item *
107            
108             C<pre> - Surround result by C<< <pre>...</pre> >> tags. Default value: 1 (enabled).
109            
110             =item *
111            
112             C<syntax> - Selects the shell syntax. Check the documentation about the
113             C<syntax()> method in C<Shell::Parser> documentation for more information
114             on the available syntaxes. Default value: C<bourne>.
115            
116             =item *
117            
118             C<tabs> - When given a non-nul value, converts tabulations to this number of
119             spaces. Default value: 4.
120            
121             =back
122            
123             B<Example>
124            
125             To avoid surrounding the result by the C<< <pre>...</pre> >> tags:
126            
127             my $highlighter = Syntax::Highlight::Shell->new(pre => 0);
128            
129             =cut
130              
131             sub new {
132 2     2 1 1834     my $self = __PACKAGE__->SUPER::new(handlers => {
133                     default => \&_generic_highlight
134                 });
135 2   33     796     my $class = ref $_[0] || $_[0]; shift;
  2         4  
136 2         8     bless $self, $class;
137                 
138 2         14     $self->{_shs_options} = { %defaults };
139 2         7     my %args = @_;
140 2         8     for my $arg (keys %defaults) {
141 8 50       26         $self->{_shs_options}{$arg} = $args{$arg} if defined $args{$arg}
142                 }
143                 
144 2         14     $self->syntax($self->{_shs_options}{syntax});
145 2         20     $self->{_shs_output} = '';
146                 
147 2         9     return $self
148             }
149              
150             =item parse()
151            
152             Parse the shell code given in argument and returns the corresponding HTML
153             code, ready for inclusion in a web page.
154            
155             B<Examples>
156            
157             $html = $highlighter->parse(q{ echo "hello world" });
158            
159             $html = $highlighter->parse(<<'END');
160             # find my name
161             if [ -f /etc/passwd ]; then
162             grep $USER /etc/passwd | awk -F: '{print $5}' /etc/passwd
163             fi
164             END
165            
166             =cut
167              
168             sub parse {
169 9     9 1 21     my $self = shift;
170                 
171             ## parse the shell command
172 9         15     $self->{_shs_output} = '';
173 9         37     $self->SUPER::parse($_[0]);
174 9         79     $self->eof;
175                 
176             ## add line numbering?
177 9 50       39     if($self->{_shs_options}{nnn}) {
178 0         0         my $i = 1;
179 0         0         $self->{_shs_output} =~ s|^|<span class="$classes{line_number}">@{[sprintf '%3d', $i++]}</span> |gm;
  0         0  
180                 }
181                 
182             ## add <pre>...</pre>?
183 9 50       42     $self->{_shs_output} = "<pre>\n" . $self->{_shs_output} . "</pre>\n" if $self->{_shs_options}{pre};
184                 
185             ## convert tabs?
186 9 50       29     $self->{_shs_output} =~ s/\t/' 'x$self->{_shs_options}{tabs}/ge if $self->{_shs_options}{tabs};
  0         0  
187                 
188 9         547     return $self->{_shs_output}
189             }
190              
191             =back
192            
193             =head2 Internal Methods
194            
195             The following methods are for internal use only.
196            
197             =over 4
198            
199             =item _generic_highlight()
200            
201             C<Shell::Parser> callback that does all the work of highlighting the code.
202            
203             =cut
204              
205             sub _generic_highlight {
206 10     10   770     my $self = shift;
207 10         30     my %args = @_;
208                 
209 10 100       30     if(index('metachar,keyword,builtin,command,variable,comment', $args{type}) >= 0) {
210 6         39         $self->{_shs_output} .= qq|<span class="$classes{$args{type}}">|
211                                           . $args{token} . qq|</span>|
212                 
213                 } else {
214 4 100       19         if($args{token} =~ /^(["'])([^"']*)\1$/) {
    100          
215 1         13             $self->{_shs_output} .= qq|<span class="$classes{quote}">$1</span>|
216                                               . qq|<span class="$classes{value}">$2</span>|
217                                               . qq|<span class="$classes{quote}">$1</span>|
218                     
219                     } elsif($args{type} eq 'assign') {
220 1         13             $args{token} =~ s|^([^=]*)=|<span class="$classes{assigned}">$1</span>=<span class="$classes{value}">|;
221 1         5             $args{token} =~ s|$|</span>|;
222 1         6             $self->{_shs_output} .= $args{token}
223                     
224                     } else {
225 2         10             $self->{_shs_output} .= $args{token}
226                     }
227                 }
228             }
229              
230             =back
231            
232             =head1 NOTES
233            
234             The resulting HTML uses CSS to colourize the syntax. Here are the classes
235             that you can define in your stylesheet.
236            
237             =over 4
238            
239             =item *
240            
241             C<.s-key> - for shell keywords, like C<if>, C<for>, C<while>, C<do>...
242            
243             =item *
244            
245             C<.s-blt> - for the builtins commands
246            
247             =item *
248            
249             C<.s-cmd> - for the external commands
250            
251             =item *
252            
253             C<.s-arg> - for the command arguments
254            
255             =item *
256            
257             C<.s-mta> - for shell metacharacters, like C<|>, C<< > >>, C<\>, C<&>
258            
259             =item *
260            
261             C<.s-quo> - for the single (C<'>) and double (C<">) quotes
262            
263             =item *
264            
265             C<.s-var> - for expanded variables: C<$VARIABLE>
266            
267             =item *
268            
269             C<.s-avr> - for assigned variables: C<VARIABLE=value>
270            
271             =item *
272            
273             C<.s-val> - for shell values (inside quotes)
274            
275             =item *
276            
277             C<.s-cmt> - for shell comments
278            
279             =back
280            
281             An example stylesheet can be found in F<examples/shell-syntax.css>.
282            
283             =head1 EXAMPLE
284            
285             Here is an example of generated HTML output. It was generated with the
286             script F<eg/highlight.pl>.
287            
288             The following shell script
289            
290             #!/bin/sh
291            
292             user="$1"
293            
294             case "$user" in
295             # check if the user is root
296             'root')
297             echo "You are the BOFH."
298             ;;
299            
300             # for normal users, grep throught /etc/passwd
301             *)
302             passwd=/etc/passwd
303             if [ -f $passwd ]; then
304             grep "$user" $passwd | awk -F: '{print $5}'
305             else
306             echo "No $passwd"
307             fi
308             esac
309            
310             will be rendered like this (using the CSS stylesheet F<eg/shell-syntax.css>):
311            
312             =begin html
313            
314             <pre>
315             <span class="s-lno"> 1</span> <span class="s-cmt">#!/bin/sh</span>
316             <span class="s-lno"> 2</span>
317             <span class="s-lno"> 3</span> <span class="s-avr">user</span>=<span class="s-val">"$1"</span>
318             <span class="s-lno"> 4</span>
319             <span class="s-lno"> 5</span> <span class="s-key">case</span> <span class="s-quo">"</span><span class="s-val">$user</span><span class="s-quo">"</span> <span class="s-key">in</span>
320             <span class="s-lno"> 6</span> <span class="s-cmt"># check if the user is root</span>
321             <span class="s-lno"> 7</span> <span class="s-quo">'</span><span class="s-val">root</span><span class="s-quo">'</span><span class="s-mta">)</span>
322             <span class="s-lno"> 8</span> echo <span class="s-quo">"</span><span class="s-val">You are the BOFH.</span><span class="s-quo">"</span>
323             <span class="s-lno"> 9</span> <span class="s-mta">;</span><span class="s-mta">;</span>
324             <span class="s-lno"> 10</span>
325             <span class="s-lno"> 11</span> <span class="s-cmt"># for normal users, grep throught /etc/passwd</span>
326             <span class="s-lno"> 12</span> *<span class="s-mta">)</span>
327             <span class="s-lno"> 13</span> <span class="s-avr">passwd</span>=<span class="s-val">/etc/passwd</span>
328             <span class="s-lno"> 14</span> <span class="s-key">if</span> [ -f <span class="s-var">$passwd</span> ]<span class="s-mta">;</span> <span class="s-key">then</span>
329             <span class="s-lno"> 15</span> grep <span class="s-quo">"</span><span class="s-val">$user</span><span class="s-quo">"</span> <span class="s-var">$passwd</span> <span class="s-mta">|</span> awk -F: <span class="s-quo">'</span><span class="s-val">{print $5}</span><span class="s-quo">'</span>
330             <span class="s-lno"> 16</span> <span class="s-key">else</span>
331             <span class="s-lno"> 17</span> echo <span class="s-quo">"</span><span class="s-val">No $passwd</span><span class="s-quo">"</span>
332             <span class="s-lno"> 18</span> <span class="s-key">fi</span>
333             <span class="s-lno"> 19</span> <span class="s-key">esac</span>
334             </pre>
335            
336             =end html
337            
338             =head1 CAVEATS
339            
340             C<Syntax::Highlight::Shell> relies on C<Shell::Parser> for parsing the shell
341             code and therefore suffers from the same limitations.
342            
343             =head1 SEE ALSO
344            
345             L<Shell::Parser>
346            
347             =head1 AUTHOR
348            
349             SE<eacute>bastien Aperghis-Tramoni, E<lt>sebastien@aperghis.netE<gt>
350            
351             =head1 BUGS
352            
353             Please report any bugs or feature requests to
354             C<bug-syntax-highlight-shell@rt.cpan.org>, or through the web interface at
355             L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Syntax-Highlight-Shell>.
356             I will be notified, and then you'll automatically be notified of
357             progress on your bug as I make changes.
358            
359             =head1 COPYRIGHT & LICENSE
360            
361             Copyright 2004 SE<eacute>bastien Aperghis-Tramoni, All Rights Reserved.
362            
363             This program is free software; you can redistribute it and/or modify it
364             under the same terms as Perl itself.
365            
366             =cut
367              
368             1; # End of Syntax::Highlight::Shell
369