File Coverage

lib/Google/RestApi/SheetsApi4/Types.pm
Criterion Covered Total %
statement 33 33 100.0
branch 5 6 83.3
condition 1 3 33.3
subroutine 10 10 100.0
pod n/a
total 49 52 94.2


line stmt bran cond sub pod time code
1              
2             # custom type constrants. see Type::Library.
3             # NOTE: can't use Google::RestApi::Setup here because that module imports this one.
4              
5             # this handles the complex coercions of different formats for specifying ranges
6             # using type::library to handle it. having different formats for ranges is probably
7             # more comlicated that it's worth, it grew in complexity organically, but it works
8             # and we have it now.
9              
10             use strict;
11 1     1   5942 use warnings;
  1         2  
  1         27  
12 1     1   5  
  1         2  
  1         37  
13             our $VERSION = '1.0.2';
14              
15             use feature qw( state );
16 1     1   5  
  1         1  
  1         49  
17             use Type::Params qw( compile );
18 1     1   12 use Types::Standard qw( Undef Defined Value Str StrMatch Int ArrayRef HashRef Tuple Dict HasMethods );
  1         2  
  1         5  
19 1     1   232 use Types::Common::Numeric qw( PositiveInt PositiveOrZeroInt );
  1         2  
  1         5  
20 1     1   1680  
  1         3  
  1         6  
21             use Google::RestApi::Types qw( :all );
22 1     1   429  
  1         4  
  1         5  
23             my @types = qw(
24             DimCol DimRow DimColRow DimAll
25             RangeCol RangeRow RangeCell RangeAny RangeAll
26             RangeNamed RangeIndex
27             HasRange
28             );
29              
30             use Type::Library -base, -declare => @types;
31 1     1   4989  
  1         2  
  1         5  
32             use Exporter;
33 1     1   246 our %EXPORT_TAGS = (all => \@types);
  1         2  
  1         1923  
34              
35             my $meta = __PACKAGE__->meta;
36              
37              
38             my $dim_col = $meta->add_type(
39             name => 'DimCol',
40             parent => StrMatch[qr/^(col)/i],
41             message => sub { "Must be spreadsheet dimension 'col'" },
42             );
43              
44             my $dim_row = $meta->add_type(
45             name => 'DimRow',
46             parent => StrMatch[qr/^(row)/i],
47             message => sub { "Must be spreadsheet dimension 'row'" },
48             );
49              
50             my $dim_all = $meta->add_type(
51             name => 'DimAll',
52             parent => StrMatch[qr/^(all)/i],
53             message => sub { "Must be spreadsheet dimension 'all'" },
54             );
55              
56             $meta->add_type(
57             name => 'DimColRow',
58             parent => $dim_col | $dim_row,
59             message => sub { "Must be a spreadsheet dimension (col or row)" },
60             );
61              
62             $_->coercion->add_type_coercions(
63             Str, sub { lc(substr($_, 0, 3)); },
64             ) for ($dim_col, $dim_row, $dim_all);
65              
66              
67              
68              
69             my $col_str_int = StrMatch[qr/^([A-Z]+|\d+)$/];
70              
71             my $col = $meta->add_type(
72             name => 'RangeCol',
73             parent => StrMatch[qr/^([A-Z]+)\d*:\1\d*$/],
74             message => sub { "Must be a spreadsheet range column Ax:Ay" },
75             );
76             $col->coercion->add_type_coercions(
77             StrMatch[qr/^([A-Z]+)$/], sub { "$_:$_"; }, # 'A' => 'A:A', 1 should be a row.
78             Dict[col => $col_str_int], sub { $_ = _col_i2a($_->{col}); "$_:$_"; },
79             Tuple[Dict[col => $col_str_int]], sub { $_ = _col_i2a($_->[0]->{col}); "$_:$_"; },
80             Tuple[$col_str_int], sub { $_ = _col_i2a($_->[0]); "$_:$_"; },
81             Tuple[$col_str_int, False], sub { $_ = _col_i2a($_->[0]); "$_:$_"; },
82             Tuple[Tuple[$col_str_int]], sub { $_ = _col_i2a($_->[0]->[0]); "$_:$_"; },
83             Tuple[Tuple[$col_str_int, False]], sub { $_ = _col_i2a($_->[0]->[0]); "$_:$_"; },
84             );
85             my $col = shift;
86             return '' if !defined $col || $col eq '';
87 107     107   262 return $col if $col =~ qr/\D/;
88 107 50 33     625 my $l = int($col / 27);
89 107 100       678 my $r = $col - $l * 26;
90 66         238 return $l > 0 ? (pack 'CC', $l+64, $r+64) : (pack 'C', $r+64);
91 66         166 }
92 66 100       541  
93              
94             my $row = $meta->add_type(
95             name => 'RangeRow',
96             parent => StrMatch[qr/^[A-Z]*(\d+):[A-Z]*\1$/],
97             message => sub { "Must be a spreadsheet range row x1:y1" },
98             );
99             $row->coercion->add_type_coercions(
100             PositiveInt, sub { "$_:$_"; }, # 1 => 1:1
101             Dict[row => PositiveInt], sub { "$_->{row}:$_->{row}"; },
102             Tuple[Dict[row => PositiveInt]], sub { "$_->[0]->{row}:$_->[0]->{row}"; },
103             Tuple[False, PositiveInt] => sub { "$_->[1]:$_->[1]"; },
104             Tuple[Tuple[False, PositiveInt]] => sub { "$_->[0]->[1]:$_->[0]->[1]"; },
105             );
106              
107              
108              
109             my $cell_str_int = StrMatch[qr/^[A-Z]+\d+$/];
110              
111             my $cell = $meta->add_type(
112             name => 'RangeCell',
113             parent => $cell_str_int,
114             message => sub { "Must be a spreadsheet range cell A1" },
115             );
116             $cell->coercion->add_type_coercions(
117             StrMatch[qr/^([A-Z]+\d+):\1$/], sub { (split(':'))[0]; }, # 'A1:A1' should be a cell.
118             Dict[col => $col_str_int, row => PositiveInt], sub { _col_i2a($_->{col}) . $_->{row}; },
119             Tuple[Dict[col => $col_str_int, row => PositiveInt]], sub { _col_i2a($_->[0]->{col}) . $_->[0]->{row}; },
120             Tuple[$col_str_int, PositiveInt], sub { _col_i2a($_->[0]) . $_->[1]; },
121             Tuple[Tuple[$col_str_int, PositiveInt]], sub { _col_i2a($_->[0]->[0]) . $_->[0]->[1]; },
122             );
123              
124              
125             my $range_any = $meta->add_type(
126             name => 'RangeAny',
127             parent => StrMatch[qr/^[A-Z]*\d*(:[A-Z]*\d*)?$/],
128             message => sub { "Must be a spreadsheet range A1:B2" },
129             );
130             # drive the coercions on each type by running them through compile/check.
131             $range_any->coercion->add_type_coercions(
132             Tuple[Defined, Defined],
133             sub {
134             state $check = compile(Tuple[$col | $row | $cell, $col | $row | $cell]);
135             my ($range) = $check->($_);
136              
137             # these look odd but if 'A' is passed as one of the tuples, it will be
138             # translated to 'A:A' for that one tuple by col coercions above,
139             # so have to squash it here. same goes for row.
140             ($range->[0]) = (split(':', $range->[0]))[0];
141             ($range->[1]) = (split(':', $range->[1]))[0];
142            
143             # if this is 'A1:A1' squash it to 'A1'.
144             return $range->[0] if
145             $range->[0] =~ qr/^[A-Z]+\d+/ &&
146             $range->[1] =~ qr/^[A-Z]+\d+/ &&
147             $range->[0] eq $range->[1];
148              
149             return "$range->[0]:$range->[1]";
150             },
151             Tuple[Defined],
152             sub {
153             state $check = compile(Tuple[$col | $row | $cell]);
154             my ($range) = $check->($_);
155             return $range;
156             },
157             Value,
158             sub {
159             state $check = compile($col | $row | $cell);
160             my ($range) = $check->($_);
161             return $range;
162             },
163             );
164              
165              
166              
167             $meta->add_type(
168             name => 'RangeAll',
169             parent => $col | $row | $cell | $range_any,
170             message => sub { "Must be a spreadsheet range, col, row, or cell" },
171             );
172              
173              
174              
175             # https://support.google.com/docs/answer/63175?co=GENIE.Platform%3DDesktop&hl=en
176             $meta->add_type(
177             name => 'RangeNamed',
178             parent => StrMatch[qr/^[A-Za-z_][A-Za-z0-9_]+/],
179             message => sub { "Must be a spreadsheet named range" },
180             );
181              
182             $meta->add_type(
183             name => 'RangeIndex',
184             parent => PositiveOrZeroInt,
185             message => sub { "Must be a spreadsheet range index (0-based)" },
186             );
187              
188              
189             $meta->add_type(
190             name => 'HasRange',
191             parent => HasMethods[qw(range)],
192             message => sub { "Must be a range object"; }
193             );
194              
195             __PACKAGE__->make_immutable;
196              
197             1;