File Coverage

blib/lib/Bot/ChatBots/Telegram/Keyboard.pm
Criterion Covered Total %
statement 89 96 92.7
branch 32 42 76.1
condition 6 10 60.0
subroutine 19 19 100.0
pod 4 4 100.0
total 150 171 87.7


line stmt bran cond sub pod time code
1             package Bot::ChatBots::Telegram::Keyboard;
2 2     2   73947 use strict;
  2         13  
  2         63  
3 2     2   10 use warnings;
  2         4  
  2         85  
4             { our $VERSION = '0.014'; }
5              
6 2     2   448 use Ouch;
  2         4511  
  2         9  
7 2     2   1093 use Log::Any qw< $log >;
  2         16626  
  2         9  
8 2     2   4822 use Data::Dumper;
  2         6852  
  2         113  
9              
10 2     2   1171 use Moo;
  2         20079  
  2         10  
11 2     2   4491 use namespace::clean;
  2         23074  
  2         15  
12              
13 2     2   789 use Exporter qw< import >;
  2         4  
  2         473  
14             our @EXPORT_OK = qw< keyboard >;
15              
16             has displayable => (
17             is => 'ro',
18             required => 1,
19             );
20              
21             has id => (
22             is => 'ro',
23             default => sub { return 0 },
24             isa => sub {
25             my $n = shift;
26             my $complaint = 'keyboard_id MUST be an unsigned 32 bits integer';
27             ouch 500, $complaint unless $n =~ m{\A(?: 0 | [1-9]\d* )\z}mxs;
28             my $r = unpack 'N', pack 'N', $n;
29             ouch 500, $complaint unless $n eq $r;
30             return;
31             },
32             );
33              
34             has _value_for => (
35             is => 'ro',
36             required => 1,
37             );
38              
39             {
40             my ($ONE, $ZERO, $BOUNDARY);
41              
42             BEGIN {
43 2     2   11 $ONE = "\x{200B}";
44 2         6 $ZERO = "\x{200C}";
45 2         2251 $BOUNDARY = "\x{200D}";
46             } ## end BEGIN
47              
48             sub __encode_uint32 {
49 18     18   30 my $x = shift;
50 18         81 (my $b = unpack 'B32', pack 'N', $x) =~ s/^0+//mxs;
51 18 100       42 $b = '0' unless length $b;
52 18 100       51 return join '', map { $_ ? $ONE : $ZERO } split //, $b;
  58         154  
53             } ## end sub __encode_uint32
54              
55             sub __decode_uint32 {
56 12     12   24 my $x = shift;
57 12 100       32 my $b = join '', map { $_ eq $ONE ? '1' : '0' } split //, $x;
  42         95  
58 12         33 $b = substr(('0' x 32) . $b, -32, 32);
59 12         58 return unpack 'N', pack 'B32', $b;
60             } ## end sub __decode_uint32
61              
62             sub __encode {
63 9     9   19 my ($label, $keyboard_id, $code) = @_;
64 9         19 return join '', $label,
65             $BOUNDARY, __encode_uint32($keyboard_id),
66             $BOUNDARY, __encode_uint32($code),
67             $BOUNDARY;
68             } ## end sub __encode
69              
70             sub __decode {
71 13 100   13   31 return unless defined $_[0];
72 8         159 my ($label, $kid, $code) = $_[0] =~ m{
73             \A
74             (.*)
75             $BOUNDARY ((?:$ZERO|$ONE)+)
76             $BOUNDARY ((?:$ZERO|$ONE)+)
77             $BOUNDARY
78             \z
79             }mxs;
80 8 100       28 return unless defined $code;
81 6         15 return ($label, __decode_uint32($kid), __decode_uint32($code));
82             } ## end sub __decode
83             }
84              
85             sub BUILDARGS {
86 3     3 1 1768 my ($class, %args) = @_;
87 3 100       17 ouch 500, 'no input keyboard' unless exists $args{keyboard};
88 1   50     4 my $id = $args{id} //= 0;
89 1         5 @args{qw} = __keyboard($args{keyboard}, $id);
90 1         24 return \%args;
91             } ## end sub BUILDARGS
92              
93             sub _decode {
94 13     13   29 my ($self, $x, $name) = @_;
95 13 100       43 if (ref($x) eq 'HASH') {
    50          
96 8 100       24 $x = $x->{payload} if exists $x->{payload};
97 8   100     30 $x = $x->{text} // undef;
98             }
99             elsif (ref($x)) {
100 0         0 ouch 500, "$name(): pass either hash references or plain scalars";
101             }
102              
103 13         29 return __decode($x);
104             } ## end sub _decode
105              
106             sub get_value {
107 10     10 1 11163 my ($self, $x) = @_;
108 10         28 my (undef, undef, $code) = $self->_decode($x, 'get_value');
109 10 100       39 return undef unless defined $code;
110              
111 3         11 my $vf = $self->_value_for;
112 3 50       8 if (!exists($vf->{$code})) {
113 0         0 $log->warn("get_value(): received code $code is unknown");
114 0         0 return undef;
115             }
116 3         14 return $vf->{$code};
117             } ## end sub get_value
118              
119             sub get_keyboard_id {
120 3     3 1 2523 my ($self, $x) = @_;
121 3         9 my (undef, $keyboard_id) = $self->_decode($x, 'get_keyboard_id');
122 3         13 return $keyboard_id;
123             }
124              
125             sub __keyboard {
126 1     1   4 my ($input, $keyboard_id) = @_;
127 1 50       4 ouch 500, 'invalid input keyboard, not an ARRAY'
128             unless ref($input) eq 'ARRAY';
129 1 50       5 ouch 500, 'invalid empty keyboard' unless @$input;
130              
131 1         3 my $code = 0;
132 1         2 my @display_keyboard;
133 1         2 my (%value_for, %code_for);
134 1         3 for my $row (@$input) {
135 3 50       10 ouch 500, 'invalid input keyboard, not an AoA'
136             unless ref($row) eq 'ARRAY';
137              
138 3         5 my @display_row;
139 3         6 push @display_keyboard, \@display_row;
140 3         7 for my $item (@$row) {
141 10 50       23 ouch 500, 'invalid input keyboard, not an AoAoH'
142             unless ref($item) eq 'HASH';
143              
144 10         32 my %display_item = %$item;
145 10         19 push @display_row, \%display_item;
146              
147 10         21 my $command = delete $display_item{_value};
148 10 100       22 next unless defined $command;
149 9   66     46 my $cc = $code_for{$command} //= $code++;
150 9   33     42 $value_for{$cc} //= $command;
151             $display_item{text} =
152 9         17 __encode($display_item{text}, $keyboard_id, $cc);
153             } ## end for my $item (@$row)
154             } ## end for my $row (@$input)
155 1         6 return (\@display_keyboard, \%value_for);
156             } ## end sub __keyboard
157              
158             sub keyboard {
159 3     3 1 3464 my %args;
160 3 100       20 if (@_ > 1) {
    100          
161 1 50       4 if (ref($_[0])) {
162 0         0 $args{keyboard} = [@_];
163             }
164             else {
165 1         4 %args = @_;
166             }
167             } ## end if (@_ > 1)
168             elsif (@_ == 1) {
169 1         2 my $x = shift;
170 1 50       4 if (@$x > 0) {
171 0 0       0 if (ref($x->[0]) eq 'ARRAY') {
172 0         0 $args{keyboard} = $x;
173             }
174             else {
175 0         0 $args{keyboard} = [$x]; # one row only
176             }
177             } ## end if (@$x > 0)
178             } ## end elsif (@_ == 1)
179 3         97 return Bot::ChatBots::Telegram::Keyboard->new(%args);
180             } ## end sub keyboard
181              
182             1;