File Coverage

blib/lib/Syntax/Highlight/JSON2.pm
Criterion Covered Total %
statement 84 110 76.3
branch 29 50 58.0
condition 6 18 33.3
subroutine 19 25 76.0
pod 2 2 100.0
total 140 205 68.2


line stmt bran cond sub pod time code
1 1     1   24 use 5.008;
  1         3  
  1         48  
2 1     1   6 use strict;
  1         3  
  1         44  
3 1     1   4 use warnings;
  1         3  
  1         147  
4              
5             {
6             package Syntax::Highlight::JSON2;
7              
8             our $AUTHORITY = 'cpan:TOBYINK';
9             our $VERSION = '0.003';
10            
11 1         20 use MooX::Struct -retain, -rw,
12             Feature => [],
13             Token => [-extends => [qw<Feature>], qw($spelling!)],
14             Brace => [-extends => [qw<Token>]],
15             Bracket => [-extends => [qw<Token>]],
16             String => [-extends => [qw<Token>]],
17             Number => [-extends => [qw<Token>]],
18             Number_Double => [-extends => [qw<Number>]],
19             Number_Decimal => [-extends => [qw<Number>]],
20             Number_Integer => [-extends => [qw<Number>]],
21             Punctuation => [-extends => [qw<Token>]],
22             Keyword => [-extends => [qw<Token>]],
23             Boolean => [-extends => [qw<Keyword>]],
24             Whitespace => [-extends => [qw<Token>]],
25             Unknown => [-extends => [qw<Token>]],
26 1     1   4 ;
  1         2  
27              
28             use Throwable::Factory
29 1         11 Tokenization => [qw( $remaining -caller )],
30             NotImplemented => [qw( -notimplemented )],
31             WTF => [],
32             WrongInvocant => [qw( -caller )],
33 1     1   1128 ;
  1         2  
34              
35             {
36 1     1   526 use HTML::HTML5::Entities qw/encode_entities/;
  1         1  
  1         57  
37            
38 1     1   4 no strict 'refs';
  1         2  
  1         355  
39 0     0   0 *{Feature . "::tok"} = sub { sprintf "%s~", $_[0]->TYPE };
40 0     0   0 *{Token . "::tok"} = sub { sprintf "%s[%s]", $_[0]->TYPE, $_[0]->spelling };
41 0     0   0 *{Whitespace . "::tok"} = sub { $_[0]->TYPE };
42 0     0   0 *{Feature . "::TO_STRING"} = sub { "" };
43 0     0   0 *{Token . "::TO_STRING"} = sub { $_[0]->spelling };
44             *{Token . "::TO_HTML"} = sub {
45 29     29   1014 sprintf "<span class=\"json_%s\">%s</span>", lc $_[0]->TYPE, encode_entities($_[0]->spelling)
46             };
47 21     21   2433 *{Whitespace . "::TO_HTML"} = sub { $_[0]->spelling };
48             }
49              
50             our %STYLE = (
51             json_brace => 'color:#990000;font-weight:bold',
52             json_bracket => 'color:#990000;font-weight:bold',
53             json_punctuation => 'color:#990000;font-weight:bold',
54             json_string => 'color:#cc00cc',
55             json_keyword => 'color:#cc00cc;font-weight:bold;font-style:italic',
56             json_boolean => 'color:#cc00cc;font-weight:bold;font-style:italic',
57             json_unknown => 'color:#ffff00;background-color:#660000;font-weight:bold',
58             json_number_double => 'color:#cc00cc;font-weight:bold',
59             json_number_decimal => 'color:#cc00cc;font-weight:bold',
60             json_number_integer => 'color:#cc00cc;font-weight:bold',
61             );
62              
63 1     1   6 use Moo;
  1         2  
  1         7  
64              
65             has _tokens => (is => 'rw');
66             has _remaining => (is => 'rw');
67            
68 1     1   392 use IO::Detect qw( as_filehandle );
  1         2  
  1         12  
69 1     1   476 use Scalar::Util qw( blessed );
  1         3  
  1         1593  
70            
71             sub _peek
72             {
73 154     154   199 my $self = shift;
74 154         174 my ($regexp) = @_;
75 154 100       470 $regexp = qr{^(\Q$regexp\E)} unless ref $regexp;
76            
77 154 100       160 if (my @m = (${$self->_remaining} =~ $regexp))
  154         754  
78             {
79 50         169 return \@m;
80             }
81            
82 104         390 return;
83             }
84              
85             sub _pull_token
86             {
87 50     50   6280 my $self = shift;
88 50         100 my ($spelling, $class, %more) = @_;
89 50 50       95 defined $spelling or WTF->throw("Tried to pull undef token!");
90 50         51 substr(${$self->_remaining}, 0, length $spelling, "");
  50         115  
91 50         57 push @{$self->_tokens}, $class->new(spelling => $spelling, %more);
  50         1122  
92             }
93              
94             sub _pull_whitespace
95             {
96 21     21   24 my $self = shift;
97 21         119 $self->_pull_token($1, Whitespace)
98 21 50       22 if ${$self->_remaining} =~ m/^(\s*)/sm;
99             }
100            
101             sub _pull_string
102             {
103 8     8   11 my $self = shift;
104             # Extract string with escaped characters
105 8 50       7 ${$self->_remaining} =~ m#^("((?:[^\x00-\x1F\\"]|\\(?:["\\/bfnrt]|u[[:xdigit:]]{4})){0,32766})*")#
  8         85  
106             ? $self->_pull_token($1, String)
107             : $self->_pull_token('"', Unknown);
108             }
109            
110             sub _serializer
111             {
112 0     0   0 require RDF::Trine::Serializer::RDFJSON;
113 0         0 return "RDF::Trine::Serializer::RDFJSON";
114             }
115            
116             sub _scalarref
117             {
118 1     1   3 my $self = shift;
119 1         1 my ($thing) = @_;
120            
121 1 50 33     19 if (blessed $thing and $thing->isa("RDF::Trine::Model"))
122             {
123 0         0 $thing = $thing->as_hashref;
124             }
125            
126 1 0 33     5 if (blessed $thing and $thing->isa("RDF::Trine::Iterator") and $thing->can("as_json"))
      33        
127             {
128 0         0 my $t = $thing->as_json;
129 0         0 $thing = \$t
130             }
131            
132 1 0 33     6 if (blessed $thing and $thing->isa("RDF::Trine::Iterator") and $self->can("_serializer"))
      33        
133             {
134 0         0 my $t = $self->_serializer->new->serialize_iterator_to_string($thing);
135 0         0 $thing = \$t
136             }
137            
138 1 50 33     10 if (!blessed $thing and ref $thing =~ /^(HASH|ARRAY)$/)
139             {
140 0         0 require JSON;
141 0         0 my $t = JSON::to_json($thing, { pretty => 1, canonical => 1 });
142 0         0 $thing = \$t;
143             }
144            
145 1 50       97 unless (ref $thing eq 'SCALAR')
146             {
147 0         0 my $fh = as_filehandle($thing);
148 0         0 local $/;
149 0         0 my $t = <$fh>;
150 0         0 $thing = \$t;
151             }
152            
153 1         6 return $thing;
154             }
155            
156             sub tokenize
157             {
158 1     1 1 2 my $self = shift;
159 1 50       4 ref $self or WrongInvocant->throw("this is an object method!");
160            
161 1         6 $self->_remaining( $self->_scalarref(@_) );
162 1         3 $self->_tokens([]);
163            
164             # Declare this ahead of time for use in the big elsif!
165 1         2 my $matches;
166            
167 1         1 while (length ${ $self->_remaining })
  30         3017  
168             {
169 29 50       97 $self->_pull_whitespace if $self->_peek(qr{^\s+});
170            
171 29 100       99 if ($matches = $self->_peek(qr!^([\,\:])!))
    100          
    100          
    100          
    50          
    100          
    50          
    0          
    0          
172             {
173 9         23 $self->_pull_token($matches->[0], Punctuation);
174             }
175             elsif ($matches = $self->_peek(qr!^([\[\]])!))
176             {
177 2         9 $self->_pull_token($matches->[0], Bracket);
178             }
179             elsif ($matches = $self->_peek(qr!^( \{ | \} )!x))
180             {
181 8         26 $self->_pull_token($matches->[0], Brace);
182             }
183             elsif ($self->_peek("null"))
184             {
185 1         5 $self->_pull_token("null", Keyword);
186             }
187             elsif ($matches = $self->_peek(qr!^(true|false)!))
188             {
189 0         0 $self->_pull_token($matches->[0], Boolean);
190             }
191             elsif ($self->_peek('"'))
192             {
193 8         18 $self->_pull_string;
194             }
195             elsif ($matches = $self->_peek(qr!^([-]?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?)!))
196             {
197 1         3 my $n = $matches->[0];
198 1 50       10 if ($n =~ /e/i) { $self->_pull_token($n, Number_Double) }
  0 50       0  
199 1         5 elsif ($n =~ /\./) { $self->_pull_token($n, Number_Decimal) }
200 0         0 else { $self->_pull_token($n, Number_Integer) }
201             }
202             elsif ($matches = $self->_peek(qr/^([^\s\r\n]+)[\s\r\n]/ms))
203             {
204 0         0 $self->_pull_token($matches->[0], Unknown);
205             }
206             elsif ($matches = $self->_peek(qr/^([^\s\r\n]+)$/ms))
207             {
208 0         0 $self->_pull_token($matches->[0], Unknown);
209             }
210             else
211             {
212 0         0 Tokenization->throw(
213             "Could not tokenise string!",
214 0         0 remaining => ${ $self->_remaining },
215             );
216             }
217            
218 29 100       12092 $self->_pull_whitespace if $self->_peek(qr{^\s+});
219             }
220            
221 1         5 return $self->_tokens;
222             }
223            
224             sub highlight
225             {
226 1     1 1 969 my $self = shift;
227 1 50       5 ref $self or WrongInvocant->throw("this is an object method!");
228            
229 1         7 $self->tokenize(@_);
230 1         2 return join "", map $_->TO_HTML, @{$self->_tokens};
  1         11  
231             }
232             }
233              
234             1;
235              
236             __END__
237              
238             =pod
239              
240             =encoding utf-8
241              
242             =head1 NAME
243              
244             Syntax::Highlight::JSON2 - syntax highlighting for JSON
245              
246             =head1 SYNOPSIS
247              
248             use Syntax::Highlight::JSON2;
249             my $syntax = "Syntax::Highlight::JSON2"->new;
250             print $syntax->highlight($filehandle);
251              
252             =head1 DESCRIPTION
253              
254             Outputs pretty syntax-highlighted HTML for JSON. (Actually just
255             adds C<< <span> >> elements with C<< class >> attributes. You're expected to
256             bring your own CSS.)
257              
258             There's nothing significant in the number "2" in the name of this module.
259             There was just already a L<Syntax::Highlight::JSON> on CPAN, which seems
260             completely undocumented so I'm a little scared to use it!
261              
262             =head2 Methods
263              
264             =over
265              
266             =item C<< highlight($input) >>
267              
268             Highlight some JSON.
269              
270             C<< $input >> may be a file handle, filename or a scalar ref of text.
271              
272             Returns a string of HTML.
273              
274             =item C<< tokenize($input) >>
275              
276             This is mostly intended for subclassing Syntax::Highlight::JSON.
277              
278             C<< $input >> may be a file handle, filename or a scalar ref of text.
279              
280             Returns an arrayref of token objects. The exact API for the token objects
281             is subject to change, but currently they support C<< TYPE >> and
282             C<< spelling >> methods.
283              
284             =back
285              
286             =head1 BUGS
287              
288             Please report any bugs to
289             L<http://rt.cpan.org/Dist/Display.html?Queue=Syntax-Highlight-RDF>.
290              
291             =head1 SEE ALSO
292              
293             L<Syntax::Highlight::RDF>,
294             L<Syntax::Highlight::XML>.
295              
296             =head1 AUTHOR
297              
298             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
299              
300             =head1 COPYRIGHT AND LICENCE
301              
302             This software is copyright (c) 2013 by Toby Inkster.
303              
304             This is free software; you can redistribute it and/or modify it under
305             the same terms as the Perl 5 programming language system itself.
306              
307             =head1 DISCLAIMER OF WARRANTIES
308              
309             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
310             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
311             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
312