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