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   55645 use strict;
  2         11  
  2         47  
3 2     2   8 use warnings;
  2         3  
  2         68  
4             { our $VERSION = '0.012'; }
5              
6 2     2   351 use Ouch;
  2         3453  
  2         7  
7 2     2   820 use Log::Any qw< $log >;
  2         12707  
  2         7  
8 2     2   3851 use Data::Dumper;
  2         5195  
  2         85  
9              
10 2     2   890 use Moo;
  2         15375  
  2         8  
11 2     2   3204 use namespace::clean;
  2         18138  
  2         10  
12              
13 2     2   574 use Exporter qw< import >;
  2         5  
  2         353  
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   7 $ONE = "\x{200B}";
44 2         3 $ZERO = "\x{200C}";
45 2         1781 $BOUNDARY = "\x{200D}";
46             } ## end BEGIN
47              
48             sub __encode_uint32 {
49 18     18   32 my $x = shift;
50 18         66 (my $b = unpack 'B32', pack 'N', $x) =~ s/^0+//mxs;
51 18 100       35 $b = '0' unless length $b;
52 18 100       32 return join '', map { $_ ? $ONE : $ZERO } split //, $b;
  58         113  
53             } ## end sub __encode_uint32
54              
55             sub __decode_uint32 {
56 12     12   17 my $x = shift;
57 12 100       26 my $b = join '', map { $_ eq $ONE ? '1' : '0' } split //, $x;
  42         71  
58 12         26 $b = substr(('0' x 32) . $b, -32, 32);
59 12         42 return unpack 'N', pack 'B32', $b;
60             } ## end sub __decode_uint32
61              
62             sub __encode {
63 9     9   13 my ($label, $keyboard_id, $code) = @_;
64 9         13 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   24 return unless defined $_[0];
72 8         113 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       21 return unless defined $code;
81 6         8 return ($label, __decode_uint32($kid), __decode_uint32($code));
82             } ## end sub __decode
83             }
84              
85             sub BUILDARGS {
86 3     3 1 1196 my ($class, %args) = @_;
87 3 100       10 ouch 500, 'no input keyboard' unless exists $args{keyboard};
88 1   50     3 my $id = $args{id} //= 0;
89 1         2 @args{qw} = __keyboard($args{keyboard}, $id);
90 1         17 return \%args;
91             } ## end sub BUILDARGS
92              
93             sub _decode {
94 13     13   22 my ($self, $x, $name) = @_;
95 13 100       28 if (ref($x) eq 'HASH') {
    50          
96 8 100       28 $x = $x->{payload} if exists $x->{payload};
97 8   100     19 $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         24 return __decode($x);
104             } ## end sub _decode
105              
106             sub get_value {
107 10     10 1 8367 my ($self, $x) = @_;
108 10         17 my (undef, undef, $code) = $self->_decode($x, 'get_value');
109 10 100       28 return undef unless defined $code;
110              
111 3         7 my $vf = $self->_value_for;
112 3 50       7 if (!exists($vf->{$code})) {
113 0         0 $log->warn("get_value(): received code $code is unknown");
114 0         0 return undef;
115             }
116 3         10 return $vf->{$code};
117             } ## end sub get_value
118              
119             sub get_keyboard_id {
120 3     3 1 1873 my ($self, $x) = @_;
121 3         7 my (undef, $keyboard_id) = $self->_decode($x, 'get_keyboard_id');
122 3         10 return $keyboard_id;
123             }
124              
125             sub __keyboard {
126 1     1   3 my ($input, $keyboard_id) = @_;
127 1 50       3 ouch 500, 'invalid input keyboard, not an ARRAY'
128             unless ref($input) eq 'ARRAY';
129 1 50       3 ouch 500, 'invalid empty keyboard' unless @$input;
130              
131 1         2 my $code = 0;
132 1         1 my @display_keyboard;
133 1         2 my (%value_for, %code_for);
134 1         2 for my $row (@$input) {
135 3 50       8 ouch 500, 'invalid input keyboard, not an AoA'
136             unless ref($row) eq 'ARRAY';
137              
138 3         4 my @display_row;
139 3         4 push @display_keyboard, \@display_row;
140 3         4 for my $item (@$row) {
141 10 50       18 ouch 500, 'invalid input keyboard, not an AoAoH'
142             unless ref($item) eq 'HASH';
143              
144 10         24 my %display_item = %$item;
145 10         16 push @display_row, \%display_item;
146              
147 10         14 my $command = delete $display_item{_value};
148 10 100       16 next unless defined $command;
149 9   66     23 my $cc = $code_for{$command} //= $code++;
150 9   33     29 $value_for{$cc} //= $command;
151             $display_item{text} =
152 9         13 __encode($display_item{text}, $keyboard_id, $cc);
153             } ## end for my $item (@$row)
154             } ## end for my $row (@$input)
155 1         4 return (\@display_keyboard, \%value_for);
156             } ## end sub __keyboard
157              
158             sub keyboard {
159 3     3 1 2593 my %args;
160 3 100       12 if (@_ > 1) {
    100          
161 1 50       2 if (ref($_[0])) {
162 0         0 $args{keyboard} = [@_];
163             }
164             else {
165 1         3 %args = @_;
166             }
167             } ## end if (@_ > 1)
168             elsif (@_ == 1) {
169 1         2 my $x = shift;
170 1 50       3 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         45 return Bot::ChatBots::Telegram::Keyboard->new(%args);
180             } ## end sub keyboard
181              
182             1;