File Coverage

blib/lib/Bot/Cobalt/DB/Term.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Bot::Cobalt::DB::Term;
2             our $VERSION = '0.001';
3              
4             ### FIXME: POD is out of date and not well-ordered
5              
6 1     1   22094 use 5.12.1;
  1         4  
  1         37  
7 1     1   5 use strict;
  1         2  
  1         29  
8 1     1   4 use warnings;
  1         5  
  1         27  
9 1     1   11 use Carp;
  1         5  
  1         95  
10              
11 1     1   412 use Bot::Cobalt::DB;
  0            
  0            
12             use Bot::Cobalt::Serializer;
13              
14             use Data::Dumper;
15              
16             use File::Spec;
17              
18             use Term::UI;
19             use Term::ReadLine;
20              
21             use Text::ParseWords qw/parse_line/;
22              
23             sub new {
24             my $self = {};
25             my $class = shift;
26             bless $self, $class;
27            
28             $self->{RL} = Term::ReadLine->new('cobalt2-dbterm');
29             $self->{OUT} = $self->{RL}->OUT || \*STDOUT;
30            
31             my %args = @_;
32             if (defined $args{Open}) {
33             $self->{INITIAL_PATH} = $args{Open};
34             }
35            
36             return $self;
37             }
38              
39             sub DESTROY {
40             my ($self) = @_;
41             if ($self->{CURRENT} && $self->{CURRENT}->is_open) {
42             warn "Closing DB due to DESTROY";
43             $self->{CURRENT}->dbclose;
44             }
45             }
46              
47             sub _switch_db {
48             my ($self, $dbpath) = @_;
49             my $o = $self->{OUT};
50             unless (-f $dbpath) {
51             print $o "Path $dbpath is not a normal file.\n",
52             "Perhaps you wanted to `create`?\n",
53             return
54             }
55            
56             my $db = Bot::Cobalt::DB->new(
57             File => $dbpath,
58             Raw => 1,
59             );
60            
61             unless ( $db->dbopen ) {
62             print $o "Could not open DB at $dbpath.\n";
63             return
64             }
65             my $count = $db->dbkeys;
66             print $o "Opened database $dbpath\n",
67             "Database has $count keys.\n";
68            
69             $db->dbclose;
70            
71             $self->{CURRENT} = $db;
72            
73             return 1
74             }
75              
76             sub _get_current {
77             my ($self) = @_;
78             return unless $self->{CURRENT};
79             return $self->{CURRENT}
80             }
81              
82             sub interactive {
83             my ($self) = @_;
84             $self->{RUN} = 1;
85             my $o = $self->{OUT};
86             my $t = $self->{RL};
87            
88             if (defined $self->{INITIAL_PATH}) {
89             my $path = $self->{INITIAL_PATH};
90             unless ( $self->_switch_db($path) ) {
91             print $o "Could not open initial DB at $path\n";
92             return
93             }
94             }
95            
96             PROMPT: while ($self->{RUN}) {
97             my $db = $self->{CURRENT};
98             my $dbstatus;
99             if ( $db and ref $db and $db->can('File') ) {
100             $dbstatus = (File::Spec->splitpath($db->File))[2];
101             } else {
102             $dbstatus = 'no db selected';
103             }
104             my $cmd = $t->get_reply(
105             prompt => "($dbstatus) dbterm> ",
106             default => 'help',
107             );
108            
109             if ($cmd) {
110             $t->addhistory($cmd);
111             my ($thiscmd, @args) = parse_line('\s+', 0, $cmd);
112             next PROMPT unless $thiscmd;
113             unless ( lc($thiscmd) ~~
114             [ qw/h help q quit open create freeze thaw/ ]
115             ) {
116             unless ( $self->_get_current ) {
117             print $o "No database currently open; try `help` or `open`\n";
118             next PROMPT
119             }
120             }
121            
122             my $thismethod = '_cmd_'.$thiscmd;
123             if ($self->can($thismethod)) {
124             $self->$thismethod(@args);
125             } else {
126             print $o "Unknown command, try `help`\n";
127             next PROMPT
128             }
129             }
130            
131             } ## PROMPT
132             }
133              
134              
135             sub _cmd_h { _cmd_help(@_) }
136             sub _cmd_help {
137             my ($self, $item) = @_;
138             my $o = $self->{OUT};
139              
140             my $help = {
141             open => [
142             "open ",
143             " Attempts to open the specified Bot::Cobalt::DB.",
144             ],
145              
146             copy => [
147             "copy ",
148             " Copies a value from key to .",
149             " Overwrites any existing destination key.",
150             ],
151            
152             create => [
153             "create ",
154             " Creates a new database at the specified path.",
155             ],
156              
157             current => [
158             "current",
159             " Display path to currently-selected DB",
160             " (Same as `size`)",
161             ],
162              
163             del => [
164             "del ",
165             " Deletes the specified key.",
166             ],
167              
168             freeze => [
169             "freeze ",
170             " Displays serialized Perl data structures.",
171             " A format can optionally be specified:",
172             " freeze --yaml { Some => 'Hash' }",
173             ],
174            
175             get => [
176             "get ",
177             " Retrieves the specified key.",
178             ],
179            
180             getref => [
181             "getref ",
182             " Retrieves and deserializes the specified key.",
183             ],
184            
185             grep => [
186             "grep ",
187             " Search all DB values for the specified regex.",
188             " (May lock the DB for a long time on a large DB!)",
189             ],
190            
191             keys => [
192             "keys [regex]",
193             " Lists all keys in DB.",
194             " Optionally allows searching keys by regex.",
195             ],
196              
197             put => [
198             "put ",
199             " Inserts raw data as the value of the specified key.",
200             " Note that is usually JSON.",
201             ],
202            
203             putref => [
204             "putref ",
205             " Inserts a serialized Perl data structure.",
206             " Example: putref mykey { Str => 'things', Bool => 1 }",
207             ],
208            
209             thaw => [
210             "thaw ",
211             " Displays Perl data structures thawed from frozen references.",
212             ],
213             };
214              
215             if (!$item || !defined $help->{$item}) {
216             my $cmds = join ' ', sort keys %$help;
217             print $o (join "\n",
218             "Commands: ",
219             "$cmds\n",
220             "Use `help ` for cmd usage information.\n",
221             );
222             return
223             }
224            
225             my $thishelp = join "\n", @{ $help->{$item} };
226             print $o $thishelp, "\n";
227             }
228              
229             sub _cmd_q { _cmd_quit(@_) }
230             sub _cmd_quit {
231             my ($self) = @_;
232             my $o = $self->{OUT};
233             print $o "Exiting.\n";
234             $self->{RUN} = 0;
235             }
236              
237             sub _cmd_open {
238             my ($self, $path) = @_;
239             my $t = $self->{RL};
240             my $o = $self->{OUT};
241            
242             if ($self->{CURRENT}) {
243             print $o "Switching from open DB\n";
244             $self->{CURRENT}->dbclose if $self->{CURRENT}->is_open;
245             }
246            
247             until (defined $path) {
248             $path = $t->get_reply(
249             prompt => 'Path to database: ',
250             );
251             }
252            
253             unless ( $self->_switch_db($path) ) {
254             print $o "Could not switch to DB at $path\n";
255             return
256             }
257             }
258              
259             sub _cmd_size { _cmd_current(@_) }
260             sub _cmd_sizeof { _cmd_current(@_) }
261             sub _cmd_current {
262             my ($self) = @_;
263             my $db = $self->{CURRENT};
264             my $o = $self->{OUT};
265            
266             my $current = $db->File;
267             print $o "Current DB: $current\n";
268             my $size = -s $current;
269             $size = sprintf("%.02f", $size / 1024);
270             print $o "Size: $size kbytes\n";
271             }
272              
273             sub _cmd_copy {
274             my ($self, $key, $dest) = @_;
275             my $db = $self->{CURRENT};
276             my $o = $self->{OUT};
277            
278             unless (defined $key && defined $dest) {
279             print $o "Usage: copy \n";
280             return
281             }
282            
283             unless ( $db->dbopen ) {
284             print $o "Database open failure\n";
285             return
286             }
287            
288             my $item = $db->get($key);
289            
290             unless (defined $item) {
291             print $o "No value defined for $key\n";
292             $db->dbclose;
293             return
294             }
295            
296             if ( $db->get($dest) ) {
297             print $o "!! Overwriting destination key $dest\n";
298             }
299              
300             $db->put($dest, $item);
301             $db->dbclose;
302             require bytes;
303             my $datalen = bytes::length($item);
304             print $o "Copied $datalen bytes from $key to $dest\n";
305             }
306              
307             sub _cmd_fetch { _cmd_get(@_) }
308             sub _cmd_get {
309             my ($self, $key) = @_;
310             my $db = $self->{CURRENT};
311             my $o = $self->{OUT};
312            
313             unless (defined $key) {
314             print $o "Usage: get \n";
315             return
316             }
317            
318             unless ( $db->dbopen(ro => 1) ) {
319             print $o "Database open failure\n";
320             return
321             }
322            
323             my $item = $db->get($key);
324             $db->dbclose;
325            
326             unless (defined $item) {
327             print $o "No value defined for $key\n";
328             return
329             }
330              
331             print $item ."\n";
332             }
333              
334             sub _cmd_getref {
335             my ($self, $key) = @_;
336             my $db = $self->{CURRENT};
337             my $o = $self->{OUT};
338            
339             unless (defined $key) {
340             print $o "Usage: getref \n";
341             return
342             }
343              
344             my $ser = Bot::Cobalt::Serializer->new('JSON');
345              
346             unless ( $db->dbopen(ro => 1) ) {
347             print $o "Database open failure\n";
348             return
349             }
350            
351             my $item = $db->get($key);
352             $db->dbclose;
353            
354             unless (defined $item) {
355             print $o "No value defined for $key\n";
356             return
357             }
358            
359             my $ref;
360             eval { $ref = $ser->thaw($item) };
361             if ($@) {
362             print $o "Could not thaw value; maybe not JSON?\n";
363             return
364             }
365            
366             unless (ref $ref) {
367             print $o "Thawed value not a reference\n";
368             return
369             }
370            
371             print $o Dumper $ref;
372             }
373              
374             sub _cmd_putref {
375             my ($self, $key, @data) = @_;
376             my $db = $self->{CURRENT};
377             my $o = $self->{OUT};
378              
379             unless (defined $key && @data) {
380             print $o "Usage: putref \n";
381             return
382             }
383            
384             my $datastr = join ' ', @data;
385             my $ref;
386             $ref = eval $datastr;
387             unless ($ref && ref $ref) {
388             print $o "Could not putref; input not a reference.\n";
389             return
390             }
391              
392             my $ser = Bot::Cobalt::Serializer->new('JSON');
393             my $serialized;
394             eval { $serialized = $ser->freeze($ref) };
395             if ($@) {
396             print $o "Could not serialize reference.\n";
397             return
398             }
399              
400             unless ( $db->dbopen ) {
401             print $o "Database open failure.\n";
402             return
403             }
404             $db->put($key, $serialized);
405             $db->dbclose;
406            
407             require bytes;
408             my $datalen = bytes::length($serialized);
409             print $o "Added $datalen bytes to $key\n";
410             }
411              
412             sub _cmd_set { _cmd_put(@_) }
413             sub _cmd_put {
414             my ($self, $key, @data) = @_;
415             my $db = $self->{CURRENT};
416             my $o = $self->{OUT};
417              
418             unless (defined $key && @data) {
419             print $o "Usage: put \n";
420             return
421             }
422              
423             unless ( $db->dbopen ) {
424             print $o "Database open failure.\n";
425             return
426             }
427            
428             my $datastr = join ' ', @data;
429             $db->put($key, $datastr);
430             require bytes;
431             my $datalen = bytes::length($datastr);
432             print $o "Added $datalen bytes to $key\n";
433             my $retrieved = $db->get($key);
434             unless ($retrieved eq $datastr) {
435             print $o "Warning; Re-retrieved item doesn't match original put()\n";
436             }
437             $db->dbclose;
438              
439             }
440              
441             sub _cmd_delete { _cmd_del(@_) }
442             sub _cmd_del {
443             my ($self, $key) = @_;
444             my $db = $self->{CURRENT};
445             my $o = $self->{OUT};
446              
447             unless (defined $key) {
448             print $o "Usage: del \n";
449             return
450             }
451              
452             unless ( $db->dbopen ) {
453             print $o "Database open failure.\n";
454             return
455             }
456            
457             unless ( defined $db->get($key) ) {
458             print $o "Key $key doesn't appear to exist.\n";
459             $db->dbclose;
460             return
461             }
462            
463             unless ( $db->del($key) ) {
464             print $o "del() returned false for key $key\n";
465             } else {
466             print $o "Deleted key $key\n";
467             }
468            
469             $db->dbclose;
470            
471             }
472              
473             sub _cmd_list { _cmd_keys(@_) }
474             sub _cmd_ls { _cmd_keys(@_) }
475             sub _cmd_keys {
476             my ($self, $str) = @_;
477             my $db = $self->{CURRENT};
478             my $o = $self->{OUT};
479              
480             unless ( $db->dbopen(ro => 1) ) {
481             print $o "Database open failure.\n";
482             return
483             }
484              
485             my @keys = $db->dbkeys;
486            
487             unless (@keys) {
488             print $o "Empty database.\n";
489             $db->dbclose;
490             return
491             }
492            
493             if ($str) {
494             my $re = qr/$str/;
495             my @discard = @keys;
496             @keys = ();
497             for my $thiskey (@discard) {
498             push(@keys, $thiskey)
499             if $thiskey =~ $re;
500             }
501             print $o "No matching keys found." unless @keys;
502             }
503             print $o '('.scalar @keys.' keys)' if @keys;
504             print $o join "\n", sort @keys, "\n";
505             $db->dbclose;
506             }
507              
508             sub _cmd_create {
509             my ($self, $dbpath) = @_;
510             my $o = $self->{OUT};
511             my $t = $self->{RL};
512              
513             unless ($dbpath) {
514             print $o "Usage: create \n";
515             return
516             }
517            
518             if (-e $dbpath) {
519             my $rpl = $t->ask_yn(
520             prompt => "Should I overwrite the existing file?",
521             default => 'n',
522             print_me => "That path already exists.",
523             );
524            
525             unless ($rpl) {
526             print $o "Skipping; path exists.\n";
527             return
528             } else {
529             if (-f $dbpath) {
530             unlink($dbpath);
531             } else {
532             print $o "Skipping; cannot unlink; not a regular file: $dbpath\n";
533             return
534             }
535             }
536             }
537              
538             my $db = Bot::Cobalt::DB->new(
539             File => $dbpath,
540             Raw => 1,
541             );
542            
543             unless ( $db->dbopen ) {
544             print $o "Could not open DB at $dbpath.\n";
545             return
546             }
547             ## test our db real quick
548             my $tstr = 'test scalar'.rand(666);
549             $db->put('test', $tstr);
550             my $test = $db->get('test');
551             unless ($test eq $tstr) {
552             print $o "Warning; new DB failed to return consistent value\n";
553             }
554             $db->del('test');
555             $db->dbclose;
556            
557             unless ( $self->_switch_db($dbpath) ) {
558             print $o "Could not switch to DB at $dbpath\n";
559             return
560             }
561            
562             print $o "Created and switched to new DB\n";
563             print $o "Path: $dbpath\n";
564             }
565              
566             sub _cmd_freeze {
567             my ($self, @args) = @_;
568             my $o = $self->{OUT};
569             my $t = $self->{RL};
570            
571             my $format = 'JSON';
572             if ( index($args[0], '--') == 0 ) {
573             my $f_opt = shift @args;
574             substr($f_opt, 0, 2, '');
575             given (lc($f_opt//'')) {
576             $format = 'YAMLXS' when "yaml";
577             $format = 'YAML' when "syck";
578             $format = 'XML' when "xml";
579             default { print $o "Unknown type: $f_opt\n" ; return }
580             }
581             }
582            
583             my $str = join ' ', @args;
584            
585             my $ref;
586             $ref = eval $str;
587             unless ($ref and ref $ref) {
588             print $o "Could not putref; input not a reference.\n";
589             return
590             }
591            
592            
593             my $ser = Bot::Cobalt::Serializer->new($format);
594             my $serialized;
595             eval { $serialized = $ser->freeze($ref) };
596             if ($@) {
597             print $o "Serializer could not freeze reference\n";
598             return
599             }
600            
601             print $o $serialized;
602             }
603              
604             sub _cmd_thaw {
605             my ($self, @args) = @_;
606             my $o = $self->{OUT};
607             my $t = $self->{RL};
608              
609             my $format = 'JSON';
610             if ( index($args[0], '--') == 0 ) {
611             my $f_opt = shift @args;
612             substr($f_opt, 0, 2, '');
613             given (lc($f_opt//'')) {
614             $format = 'YAMLXS' when "yaml";
615             $format = 'YAML' when "syck";
616             $format = 'XML' when "xml";
617             default { print $o "Unknown type: $f_opt\n" ; return }
618             }
619             }
620            
621             my $str = join ' ', @args;
622             my $ser = Bot::Cobalt::Serializer->new($format);
623             my $ref;
624             eval { $ref = $ser->thaw($str) };
625             if ($@) {
626             print $o "Serializer could not thaw string\n";
627             return
628             }
629            
630             print $o Dumper $ref;
631             }
632              
633             sub _cmd_grep {
634             my ($self, $regex) = @_;
635             my $o = $self->{OUT};
636             my $db = $self->{CURRENT};
637            
638             unless ($regex) {
639             print $o "Usage: grep \n";
640             return
641             }
642            
643             $regex = qr/$regex/;
644            
645             unless ( $db->dbopen(ro => 1) ) {
646             print $o "Database open failure.\n";
647             return
648             }
649              
650             my @result;
651              
652             KEY: for my $key ($db->dbkeys) {
653             my $data = $db->get($key) // next KEY;
654             push(@result, $key) if $data =~ $regex;
655             }
656            
657             $db->dbclose;
658            
659             unless (@result) {
660             print $o "No results.\n";
661             return
662             }
663            
664             print $o $_."\n" for sort @result;
665             my $count = scalar @result;
666             print $o "($count results found)\n";
667             }
668              
669             1;
670             __END__