File Coverage

blib/lib/String/TagString.pm
Criterion Covered Total %
statement 53 53 100.0
branch 29 32 90.6
condition 12 18 66.6
subroutine 8 8 100.0
pod 2 2 100.0
total 104 113 92.0


line stmt bran cond sub pod time code
1 1     1   20416 use warnings;
  1         2  
  1         29  
2 1     1   6 use strict;
  1         2  
  1         98  
3             package String::TagString;
4             {
5             $String::TagString::VERSION = '0.005';
6             }
7             # ABSTRACT: parse and emit tag strings (including tags with values)
8              
9              
10 1     1   1009 sub _raw_tag_name_re { qr{@?(?:\pL|[\d_.*])(?:\pL|[-\d_.*])*} }
  1     36   10  
  1         13  
  36         112  
11 24     24   66 sub _raw_tag_value_re { qr{(?:\pL|[-\d_.*])*} }
12              
13             sub tags_from_string {
14 19     19 1 10990 my ($class, $tagstring) = @_;
15              
16 19 100 66     122 return {} unless $tagstring and $tagstring =~ /\S/;
17              
18             # remove leading and trailing spaces
19 18         66 $tagstring =~ s/\A\s*//;
20 18         37 $tagstring =~ s/\s*\a//;
21              
22 18         66 my $quoted_re = qr{ "( (?:\\\\|\\"|\\[^\\"]|[^\\"])+ )" }x;
23 18         120 my $raw_lhs_re = $class->_raw_tag_name_re;
24 18         44 my $raw_rhs_re = $class->_raw_tag_value_re;
25              
26 18         169 my $tag_re = qr{
27             (?: ( $raw_lhs_re | $quoted_re )) # $1 = whole match; $2 = quoted part
28             ( : # $3 = entire value, with :
29             ( $raw_rhs_re | $quoted_re )? # $4 = whole match; $5 = quoted part
30             )?
31             (?:\+|\s+|\z) # end-of-string or some space or a +
32             }x;
33              
34 18         1254 my %tag;
35             my $pos;
36 18         168 while ($tagstring =~ m{\G$tag_re}g) {
37 34         530 $pos = pos $tagstring;
38 34 100       86 my $tag = defined $2 ? $2 : $1;
39 34 100       72 my $value = defined $5 ? $5 : $4;
40 34 50 66     186 $value = '' if ! defined $value and defined $3;
41 34 100       68 $value =~ s/\\"/"/g if defined $value;
42              
43 34 100       68 if (exists $tag{ $tag }) {
44 4 100       10 if (defined $tag{ $tag }) {
45 2 100 66     21 die "invalid tagstring: conflicting entries for $tag"
46             if (! defined $value) or $value ne $tag{ $tag };
47             } else {
48 2 100       16 die "invalid tagstring: conflicting entries for $tag"
49             if defined $value;
50             }
51             }
52              
53 32         565 $tag{ $tag } = $value;
54             }
55              
56 16 100 100     104 die "invalid tagstring" unless defined $pos and $pos == length $tagstring;
57              
58 13         84 return \%tag;
59             }
60              
61              
62             sub _qs {
63 24     24   41 my ($self, $type, $str) = @_;
64 24         42 my $method = "_raw_tag_$type\_re";
65 24         61 my $re = $self->$method;
66 24 100       317 return $str if $str =~ m{\A$re\z};
67 4         1513 $str =~ s/\\/\\\\/g;
68 4         10 $str =~ s/"/\\"/g;
69 4         22 return qq{"$str"};
70             }
71              
72             sub string_from_tags {
73 13     13 1 10051 my ($class, $tags) = @_;
74              
75 13 50       37 return "" unless defined $tags;
76              
77 13 50 66     70 Carp::carp("tagstring must be a hash or array reference")
      33        
78             unless (ref $tags) and ((ref $tags eq 'HASH') or (ref $tags eq 'ARRAY'));
79              
80 13 100       31 if (ref $tags eq 'ARRAY') {
81 2         197 Carp::croak("undefined tag name in array reference")
82 3 100       10 if grep { ! defined } @$tags;
83              
84 2         3 $tags = { map { $_ => undef } @$tags };
  1         5  
85             }
86              
87 12         14 my @tags;
88 12         56 for my $name (sort keys %$tags) {
89 18         1279 my $value = $tags->{$name};
90 18 100       49 push @tags, join q{:},
91             $class->_qs(name => $name),
92             (defined $value ? $class->_qs(value => $value) : ());
93             }
94              
95 12         3329 return join q{ }, @tags;
96             }
97              
98             1;
99              
100             __END__