File Coverage

blib/lib/RDF/NS.pm
Criterion Covered Total %
statement 126 149 84.5
branch 59 90 65.5
condition 14 20 70.0
subroutine 35 36 97.2
pod 14 20 70.0
total 248 315 78.7


line stmt bran cond sub pod time code
1             package RDF::NS;
2 6     6   200388 use v5.10;
  6         54  
3 6     6   29 use strict;
  6         12  
  6         122  
4 6     6   31 use warnings;
  6         8  
  6         288  
5              
6             our $VERSION = '20181102';
7              
8 6     6   48 use Scalar::Util qw(blessed reftype);
  6         12  
  6         325  
9 6     6   3049 use File::ShareDir;
  6         152244  
  6         281  
10 6     6   44 use Carp;
  6         12  
  6         315  
11 6     6   2375 use RDF::SN;
  6         15  
  6         13538  
12              
13             our $AUTOLOAD;
14             our $FORMATS = qr/ttl|n(otation)?3|sparql|xmlns|txt|beacon|json/;
15              
16             our $DATE_REGEXP = qr/^([0-9]{4})-?([0-9][0-9])-?([0-9][0-9])$/;
17              
18             sub new {
19 28 50   28 1 521 my $class = ref($_[0]) ? ref(shift) : shift;
20 28 100       115 my $from = @_ % 2 ? shift : 1;
21 28         66 my %options = @_;
22 28   100     132 my $at = $options{at} || 'any';
23 28         52 my $warn = $options{'warn'};
24 28 50       74 $from = $options{from} if exists $options{from};
25 28 100 66     131 $from = 'any' if !$from or $from eq 1;
26              
27 28 100 100     129 if ((ref($from) || '') eq 'HASH') {
28 1         4 my $self = bless $from, $class;
29 1         4 foreach my $prefix (keys %$self) {
30 2 100       8 unless( $self->SET( $prefix => $self->{$prefix}, $warn ) ) {
31 1         3 delete $self->{$prefix};
32             }
33             }
34 1         5 return $self;
35             }
36              
37 27 100       246 if ( $from =~ $DATE_REGEXP ) {
    100          
    50          
38 16         78 $at = "$1$2$3";
39 16         37 $from = 'any';
40             } elsif( $at =~ $DATE_REGEXP ) {
41 1         6 $at = "$1$2$3";
42             } elsif ( $at !~ 'any' ) {
43 0         0 croak "RDF::NS expects 'any', '1' or a date as YYYY-MM-DD";
44             }
45              
46 27         67 my $self = bless { }, $class;
47 27         77 my $fh = $self->DATA($from);
48 27         23082 foreach (<$fh>) {
49 37770         47512 chomp;
50 37770 50       62009 next if /^#/;
51 37770         94815 my ($prefix, $namespace, $date) = split "\t", $_;
52 37770 100       75752 next if ($namespace =~ m|^https?://example\.\w+?/|);
53 37676 100 66     118544 last if $date and $at ne 'any' and $date > $at;
      100        
54              
55 37659         59280 $self->SET( $prefix => $namespace, $warn );
56             }
57 27         4458 close($fh);
58              
59 27         357 $self;
60             }
61              
62             sub DATA { # TODO: document
63 27     27 0 70 my ($self, $from) = @_;
64 27 50 50     166 $from = File::ShareDir::dist_file('RDF-NS', "prefix.cc" )
65             if ($from // 'any') eq 'any';
66 27 50       5618 croak "prefix file or date not found: $from"
67             unless -f $from;
68 27 50       1292 open (my $fh, '<', $from) or croak "failed to open $from";
69 27         116 $fh;
70             }
71              
72             sub SET {
73 37661     37661 1 60109 my ($self, $prefix, $namespace, $warn) = @_;
74              
75 37661 50       101490 if ( $prefix =~ /^(isa|can|new|uri)$/ ) {
    100          
    50          
76 0 0       0 carp "Cannot support prefix '$prefix'" if $warn;
77             } elsif ( $prefix =~ /^[a-z][a-z0-9]*$/ ) {
78 37660 50       91895 if ( $namespace =~ /^[a-z][a-z0-9]*:[^"<>]*$/ ) {
    0          
79 37660         75689 $self->{$prefix} = $namespace;
80 37660         66335 return 1;
81             } elsif( $warn ) {
82 0         0 carp "Skipping invalid $prefix namespace $namespace";
83             }
84             } elsif ( $warn ) {
85 0         0 carp "Skipping unusual prefix '$prefix'";
86             }
87              
88 1         4 return;
89             }
90              
91             *LOAD = *new;
92              
93             sub COUNT {
94 9     9 0 49 scalar keys %{$_[0]};
  9         75  
95             }
96              
97             sub FORMAT {
98 13     13 0 2076 my $self = shift;
99 13   100     53 my $format = shift || "";
100 13 50       32 $format = 'TTL' if $format =~ /^n(otation)?3$/i;
101 13 100       80 if (lc($format) =~ $FORMATS) {
    50          
102 1         3 $format = uc($format);
103 1         7 $self->$format( @_ );
104             } elsif ($format eq "") {
105 12     12   72 $self->MAP( sub { $self->{$_} } , @_ );
  12         483  
106             }
107             }
108              
109             sub PREFIX {
110 3     3 1 15 my ($self, $uri) = @_;
111 3         1071 foreach my $prefix ( sort keys %$self ) {
112 1304 100       2053 return $prefix if $uri eq $self->{$prefix};
113             }
114 1         28 return;
115             }
116              
117             sub PREFIXES {
118 1     1 1 4 my ($self, $uri) = @_;
119 1         4 my @prefixes;
120 1         6 while ( my ($prefix, $namespace) = each %$self ) {
121 696 100       1659 push @prefixes, $prefix if $uri eq $namespace;
122             }
123 1         13 return(sort(@prefixes));
124             }
125              
126             sub REVERSE {
127 21     21 1 6030 RDF::SN->new($_[0]);
128             }
129              
130             sub TTL {
131 5     5 1 2145 my $self = shift;
132 5     9   31 $self->MAP( sub { "\@prefix $_: <".$self->{$_}."> ." } , @_ );
  9         39  
133             }
134              
135             sub SPARQL {
136 5     5 1 2020 my $self = shift;
137 5     9   33 $self->MAP( sub { "PREFIX $_: <".$self->{$_}.">" } , @_ );
  9         40  
138             }
139              
140             sub XMLNS {
141 6     6 1 1663 my $self = shift;
142 6     10   36 $self->MAP( sub { "xmlns:$_=\"".$self->{$_}."\"" } , @_ );
  10         54  
143             }
144              
145             sub TXT {
146 4     4 1 2084 my $self = shift;
147 4     8   23 $self->MAP( sub { "$_\t".$self->{$_} } , @_ );
  8         30  
148             }
149              
150             sub JSON {
151 1     1 0 2 my $self = shift;
152 1     2   9 $self->MAP( sub { "\"$_\": \"".$self->{$_}."\"" } , @_ );
  2         296  
153             }
154              
155             sub BEACON {
156 4     4 1 2103 my $self = shift;
157 4     8   23 $self->MAP( sub { "#PREFIX: ".$self->{$_} } , @_ );
  8         27  
158             }
159              
160             sub SELECT {
161 2     2 1 1072 my $self = shift;
162 2     3   12 $self->MAP( sub { $_ => $self->{$_} } , @_ );
  3         15  
163             }
164              
165             # functional programming rulez!
166             sub MAP {
167 39     39 1 66 my $self = shift;
168 39         55 my $code = shift;
169 39 50       108 my @ns = @_ ? (grep { $self->{$_} } map { split /[|, ]+/ } @_)
  75         199  
  52         242  
170             : keys %$self;
171 39 100       104 if (wantarray) {
172 34         1115 return map { $code->() } sort @ns;
  56         90  
173             } else {
174 5         11 local $_ = $ns[0];
175 5         16 return $code->();
176             }
177             }
178              
179             sub GET {
180 21     21 1 130 $_[1];
181             }
182              
183       5 0   sub BLANK {
184             }
185              
186             *URI = *uri;
187              
188             sub uri {
189 8     8 1 1698 my $self = shift;
190 8 100       39 return $1 if $_[0] =~ /^<([a-zA-Z][a-zA-Z+.-]*:.+)>$/;
191 7 100       34 return $self->BLANK($_[0]) if $_[0] =~ /^_(:.*)?$/;
192 4 50       35 return unless shift =~ /^([a-z][a-z0-9]*)?([:_]([^:]+))?$/;
193 4 100       21 my $ns = $self->{ defined $1 ? $1 : '' };
194 4 50       15 return unless defined $ns;
195 4 50       14 return $self->GET($ns) unless $3;
196 4         20 return $self->GET($ns.$3);
197             }
198              
199             sub AUTOLOAD {
200 52     52   21827 my $self = shift;
201 52 100       7571 return unless $AUTOLOAD =~ /^.*::([a-z][a-z0-9]*)?(_([^:]+)?)?$/;
202 23 100       108 return $self->BLANK( defined $3 ? "_:$3" : '_' ) unless $1;
    100          
203 21 50       74 my $ns = $self->{$1} or return;
204 21 100       75 my $local = defined $3 ? $3 : shift;
205 21 100       77 return $self->GET($ns) unless defined $local;
206 9         39 return $self->GET($ns.$local);
207             }
208              
209             sub UPDATE {
210 0     0 0   my ($self, $file, $date) = @_;
211              
212 0 0 0       croak "RDF::NS expects a date as YYYY-MM-DD"
213             unless $date and $date =~ $DATE_REGEXP;
214 0           $date = "$1$2$3";
215              
216 0           my $old = RDF::NS->new($file);
217 0           my (@create,@update,@delete);
218              
219 0 0         open (my $fh, '>>', $file) or croak "failed to open $file";
220 0           my @lines;
221              
222 0           while( my ($prefix,$namespace) = each %$self ) {
223 0 0         if (!exists $old->{$prefix}) {
    0          
224 0           push @create, $prefix;
225             } elsif ( $old->{$prefix} ne $namespace ) {
226 0           push @update, $prefix;
227             } else {
228 0           next;
229             }
230 0           push @lines, "$prefix\t$namespace";
231             }
232 0           while( my ($prefix,$namespace) = each %$old ) {
233 0 0         if (!exists $self->{$prefix}) {
234 0           push @delete, $prefix;
235             }
236             }
237              
238 0           print $fh "$_\t$date\n" for sort @lines;
239 0           close $fh;
240              
241             return {
242 0           create => [ sort @create ],
243             update => [ sort @update ],
244             delete => [ sort @delete ],
245             };
246             }
247              
248             1;
249             __END__