| 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__ |