| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Data::Phrasebook::Loader::JSON; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 9 |  |  | 9 |  | 432484 | use strict; | 
|  | 9 |  |  |  |  | 26 |  | 
|  | 9 |  |  |  |  | 731 |  | 
| 4 | 9 |  |  | 9 |  | 51 | use warnings FATAL => 'all'; | 
|  | 9 |  |  |  |  | 19 |  | 
|  | 9 |  |  |  |  | 1030 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | our $VERSION = '0.02'; | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | #-------------------------------------------------------------------------- | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 9 |  |  | 9 |  | 51 | use base qw( Data::Phrasebook::Loader::Base Data::Phrasebook::Debug ); | 
|  | 9 |  |  |  |  | 25 |  | 
|  | 9 |  |  |  |  | 11392 |  | 
| 11 |  |  |  |  |  |  |  | 
| 12 | 9 |  |  | 9 |  | 40816 | use Carp qw( croak ); | 
|  | 9 |  |  |  |  | 28 |  | 
|  | 9 |  |  |  |  | 670 |  | 
| 13 | 9 |  |  | 9 |  | 12067 | use File::Slurp; | 
|  | 9 |  |  |  |  | 223809 |  | 
|  | 9 |  |  |  |  | 2585 |  | 
| 14 | 9 |  |  | 9 |  | 26743 | use JSON::XS; | 
|  | 9 |  |  |  |  | 123411 |  | 
|  | 9 |  |  |  |  | 11862 |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | #-------------------------------------------------------------------------- | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | =head1 NAME | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | Data::Phrasebook::Loader::JSON - Absract your phrases with JSON. | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | use Data::Phrasebook; | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | my $q = Data::Phrasebook->new( | 
| 27 |  |  |  |  |  |  | class  => 'Fnerk', | 
| 28 |  |  |  |  |  |  | loader => 'JSON', | 
| 29 |  |  |  |  |  |  | file   => 'phrases.json', | 
| 30 |  |  |  |  |  |  | ); | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | $q->delimiters( qr{ \[% \s* (\w+) \s* %\] }x ); | 
| 33 |  |  |  |  |  |  | my $phrase = $q->fetch($keyword); | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | This class loader implements phrasebook patterns using JSON. | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | Phrases can be contained within one or more dictionaries, with each phrase | 
| 40 |  |  |  |  |  |  | accessible via a unique key. Phrases may contain placeholders, please see | 
| 41 |  |  |  |  |  |  | L for an explanation of how to use these. Groups of phrases | 
| 42 |  |  |  |  |  |  | are kept in a dictionary. In this implementation a single file is one | 
| 43 |  |  |  |  |  |  | complete dictionary. | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | An example JSON file: | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | { | 
| 48 |  |  |  |  |  |  | "dictionary1": { "key1": "value1", "key2": "value2" }, | 
| 49 |  |  |  |  |  |  | "dictionary2": { "key3": "value3", "key4": "value4" } | 
| 50 |  |  |  |  |  |  | } | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | Within the phrase text placeholders can be used, which are then replaced with | 
| 53 |  |  |  |  |  |  | the appropriate values once the get() method is called. The default style of | 
| 54 |  |  |  |  |  |  | placeholders can be altered using the delimiters() method. | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | =head1 INHERITANCE | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | L inherits from the base class | 
| 59 |  |  |  |  |  |  | L. | 
| 60 |  |  |  |  |  |  | See that module for other available methods and documentation. | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | =head1 METHODS | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =head2 load | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | Given a C, load it. C must contain a JSON map. | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | $loader->load( $file, @dict ); | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | This method is used internally by L's | 
| 71 |  |  |  |  |  |  | C method, to initialise the data store. | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | It must take a C (be it a scalar, or something more complex) | 
| 74 |  |  |  |  |  |  | and return a handle. The C is optional, should you wish to use the | 
| 75 |  |  |  |  |  |  | dictionary support. | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | =cut | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | sub load { | 
| 80 | 11 |  |  | 11 | 1 | 6471 | my ($class, $file, @dict) = @_; | 
| 81 | 11 | 100 |  |  |  | 527 | croak "No file given as argument!"          unless defined $file; | 
| 82 | 9 | 50 |  |  |  | 286 | croak "Unable to read file [$file]!"        unless -r $file; | 
| 83 | 9 |  |  |  |  | 269 | my $json = read_file( $file ); | 
| 84 | 9 | 100 |  |  |  | 1182 | croak "No data in given JSON file [$file]"  unless $json; | 
| 85 | 8 |  |  |  |  | 111 | my $data = decode_json($json); | 
| 86 | 8 | 50 |  |  |  | 29 | croak "Badly formatted JSON file [$file]"   unless $data; | 
| 87 | 8 |  |  |  |  | 42 | $class->{json} = $data; | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | # what sections are we using? | 
| 90 | 8 |  | 100 |  |  | 56 | my $key = $class->{defaultname} || ($class->dicts)[0]; | 
| 91 | 8 | 100 |  |  |  | 40 | $class->{default} = ($key ? $class->{json}->{$key} | 
| 92 |  |  |  |  |  |  | : $class->{json}); | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 8 |  |  |  |  | 22 | $class->{dict} = []; | 
| 95 | 8 | 100 |  |  |  | 29 | $class->{dict} = [$class->{defaultname}] if $class->{defaultname}; | 
| 96 | 8 | 100 |  |  |  | 49 | $class->{dict} = (ref $dict[0] ? $dict[0] : [@dict]) if scalar @dict; | 
|  |  | 100 |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | =head2 get | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | Returns the phrase stored in the phrasebook, for a given keyword. | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | my $value = $loader->get( $key ); | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | If one or more named dictionaries have been previously selected, they will be | 
| 106 |  |  |  |  |  |  | searched in order, followed by the default dictionary. The first hit on | 
| 107 |  |  |  |  |  |  | C will be returned, otherwise C is returned. | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | =cut | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | sub get { | 
| 112 | 23 |  |  | 23 | 1 | 8574 | my ($class,$key) = @_; | 
| 113 | 23 | 100 |  |  |  | 77 | return	unless($key); | 
| 114 | 18 | 100 |  |  |  | 58 | return	unless($class->{json}); | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 17 | 100 |  |  |  | 54 | my @dicts = (ref $class->{dict} ? @{$class->{dict}} : ()); | 
|  | 16 |  |  |  |  | 43 |  | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 17 |  |  |  |  | 41 | foreach ( @dicts ) { | 
| 119 | 12 | 100 | 100 |  |  | 89 | return $class->{json}->{$_}->{$key} | 
| 120 |  |  |  |  |  |  | if exists $class->{json}->{$_} | 
| 121 |  |  |  |  |  |  | and exists $class->{json}->{$_}->{$key}; | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 13 | 100 | 100 |  |  | 106 | return $class->{default}->{$key} | 
| 125 |  |  |  |  |  |  | if ref $class->{default} eq 'HASH' | 
| 126 |  |  |  |  |  |  | and exists $class->{default}->{$key}; | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 8 |  |  |  |  | 24 | return; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | =head2 dicts | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | Returns the list of dictionaries available. | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | my @dicts = $loader->dicts(); | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | This is the list of all dictionaries available in the source file. If multiple | 
| 138 |  |  |  |  |  |  | dictionaries are not being used, then an empty list will be returned. | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | =cut | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | sub dicts { | 
| 143 | 15 |  |  | 15 | 1 | 1739 | my $class = shift; | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 15 |  |  |  |  | 21 | my @keys = keys %{$class->{json}}; | 
|  | 15 |  |  |  |  | 70 |  | 
| 146 | 15 | 100 |  |  |  | 28 | if ( scalar @keys == | 
|  | 36 |  |  |  |  | 105 |  | 
| 147 | 15 |  |  |  |  | 39 | scalar grep {ref $_ eq 'HASH'} values %{$class->{json}} ) { | 
| 148 |  |  |  |  |  |  | # data source looks like it has multiple dictionaries | 
| 149 | 11 |  |  |  |  | 91 | return (sort @keys); | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 4 |  |  |  |  | 22 | return (); | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | =head2 keywords | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | Returns the list of keywords available. List is lexically sorted. | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | my @keywords = $loader->keywords( $dict ); | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | If one or more named dictionaries have been previously selected, they will be | 
| 162 |  |  |  |  |  |  | farmed for keywords, followed by the default dictionary. | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | The C argument is optional, and may be used to override the search to a | 
| 165 |  |  |  |  |  |  | single named dictionary, or a list of dictionaries if passed by reference, | 
| 166 |  |  |  |  |  |  | plus the default dictionary of course. | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | To find all available keywords in all available dictionaries, use the | 
| 169 |  |  |  |  |  |  | following: | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | $loader->keywords( [ $loader->dicts ] ); | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | =cut | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | sub keywords { | 
| 176 | 18 |  |  | 18 | 1 | 1205 | my ($class, $dict) = @_; | 
| 177 | 18 |  |  |  |  | 23 | my (%keywords, @dicts); | 
| 178 |  |  |  |  |  |  |  | 
| 179 | 18 | 100 |  |  |  | 80 | @dicts = ( (not $dict) ? (ref $class->{dict} ? @{$class->{dict}} : ()) | 
|  | 6 | 100 |  |  |  | 32 |  | 
|  |  | 100 |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | : (ref $dict) ? @$dict : ($dict) ); | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 18 |  |  |  |  | 43 | foreach my $d (@dicts) { | 
| 183 |  |  |  |  |  |  | next unless | 
| 184 | 23 | 100 | 66 |  |  | 131 | exists $class->{json}->{$d} | 
| 185 |  |  |  |  |  |  | and ref $class->{json}->{$d} eq 'HASH'; | 
| 186 | 21 |  |  |  |  | 26 | map { $keywords{$_} = 1 } keys %{$class->{json}->{$d}}; | 
|  | 63 |  |  |  |  | 140 |  | 
|  | 21 |  |  |  |  | 59 |  | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 | 18 | 100 |  |  |  | 59 | if (ref $class->{default} eq 'HASH') { | 
| 190 | 17 |  |  |  |  | 21 | map { $keywords{$_} = 1 } keys %{$class->{default}}; | 
|  | 44 |  |  |  |  | 71 |  | 
|  | 17 |  |  |  |  | 42 |  | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 18 |  |  |  |  | 88 | my @keywords = sort keys %keywords; | 
| 194 | 18 |  |  |  |  | 154 | return @keywords; | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | =head2 set_default | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | If a requested phrase is not found in the named dictionary an attempt is made | 
| 200 |  |  |  |  |  |  | to find it in the I dictionary. L loaders normally | 
| 201 |  |  |  |  |  |  | use the first dictionary in the phrasebook as the default, but as mentioned in | 
| 202 |  |  |  |  |  |  | L"DICTIONARY SUPPORT"> this does not make sense because the dictionaries in | 
| 203 |  |  |  |  |  |  | JSON phrasebooks are not ordered. | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | To override the automatically selected default dictionary use this method, and | 
| 206 |  |  |  |  |  |  | pass it a C. This value is only reset at phrasebook | 
| 207 |  |  |  |  |  |  | load time, so you'll probably need to trigger a reload: | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | $q->loader->set_default( $default_dictionary_name ); | 
| 210 |  |  |  |  |  |  | $q->loader->load( $file ); | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | To reset the loader's behaviour to automatic default dictionary selection, | 
| 213 |  |  |  |  |  |  | pass this method an undefined value, and then reload. | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | =cut | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | sub set_default { | 
| 218 | 2 |  |  | 2 | 1 | 850 | $_[0]->{defaultname} = $_[1]; | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | 1; | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | __END__ |