File Coverage

blib/lib/App/ConvertOperaBookmarksToOrg.pm
Criterion Covered Total %
statement 78 85 91.7
branch 23 32 71.8
condition 13 24 54.1
subroutine 7 7 100.0
pod 2 2 100.0
total 123 150 82.0


line stmt bran cond sub pod time code
1             package App::ConvertOperaBookmarksToOrg;
2              
3             our $DATE = '2014-10-12'; # DATE
4             our $VERSION = '0.03'; # VERSION
5              
6 1     1   548 use 5.010001;
  1         2  
  1         38  
7 1     1   3 use strict;
  1         2  
  1         24  
8 1     1   4 use warnings;
  1         1  
  1         19  
9              
10 1     1   508 use Sort::ByExample;
  1         12468  
  1         4  
11              
12             require Exporter;
13             our @ISA = qw(Exporter);
14             our @EXPORT_OK = qw(convert_opera_bookmarks_to_org
15             convert_org_to_opera_bookmarks);
16              
17             my $sorter = Sort::ByExample->sorter([
18             "ID",
19             "NAME",
20             "URL",
21             "CREATED",
22             "TARGET",
23             "MOVE_IS_COPY",
24             "SEPARATOR_ALLOWED",
25             "EXPANDED",
26             "ACTIVE",
27             "VISITED",
28             "DESCRIPTION",
29             "TRASH FOLDER",
30             "DELETABLE",
31             "PARTNERID",
32             "UNIQUEID",
33             ], sub { $a cmp $b });
34              
35             our %SPEC;
36              
37             $SPEC{convert_opera_bookmarks_to_org} = {
38             v => 1.1,
39             summary => 'Convert Opera bookmarks (bookmarks.adr) to Org document',
40             description => <<'_',
41              
42             I want to keep my Opera browser bookmarks file (`~/.opera/bookmarks.adr`) under
43             git repository, so I can synchronize them between computers. There are a few
44             annoyances though: 1) When Opera saves bookmarks file, it remove symlinks, so
45             after I have to re-symlink the file to my git repo; 2) The ID field changes
46             sporadically, creating unnecessarily large diff and merge conflicts.
47              
48             This program (and its counterpart `convert-org-to-opera-bookmarks`) is an
49             alternative to keeping Opera bookmarks file under git. You convert to .org file,
50             put the .org file under git, and convert back to .adr. The advantage is that the
51             ID field is removed so the diff is smaller and conflict reduced. Also, you can
52             more conveniently edit using Emacs/other Org editor.
53              
54             Another alternative to this program is to use the Opera Link service from Opera
55             to synchronize your bookmarks (and a few other stuffs) between devices. But note
56             that Opera has closed some of its services in the past.
57              
58             _
59             args => {
60             input => {
61             summary => 'Opera addressbook file',
62             schema => 'str*',
63             cmdline_src => 'stdin_or_files',
64             pos => 0,
65             req => 1,
66             },
67             exclude_trash => {
68             schema => 'bool',
69             cmdline_aliases => { T=>{} },
70             },
71             },
72             };
73             sub convert_opera_bookmarks_to_org {
74 1     1 1 8 my %args = @_;
75              
76 1         2 my $exclude_trash = $args{exclude_trash};
77              
78 1         1 my $in_trash;
79 1         1 my $cur_level = 1;
80 1         1 my @sections;
81             my @ct;
82 1         32 for (split /(\r?\n){2,}/, $args{input}) {
83 24 100       45 if (/^#(\w+)\r?\n(.+)/s) {
    100          
84 7         18 push @sections, [$1, $2, $_];
85             } elsif ($_ eq '-') {
86 4         6 push @sections, ["endfolder"];
87             } else {
88             # ignore, including preamble text
89             }
90             }
91 1         3 for my $section (@sections) {
92 11         14 my $sname = $section->[0];
93 11 50       16 next if $sname eq 'DELETED';
94 11 100       16 if ($sname eq 'endfolder') {
95 4         4 $cur_level--;
96 4 50       6 die "BUG: trying to decrease level to 0" if $cur_level <= 0;
97 4         5 next;
98             }
99 7         65 my %sfields = $section->[1] =~ /\s*([^=]+)=(.*)/g;
100 7 100       18 if ($sname eq 'FOLDER') {
    50          
101 4   50     11 my $name = $sfields{NAME} // '';
102 4 50 33     9 $in_trash = 1 if $name eq 'Trash' && $cur_level == 1;
103 4 100 66     17 $in_trash = 0 if $name ne 'Trash' && $cur_level == 1;
104 4 50 33     8 unless ($in_trash && $exclude_trash) {
105 4         10 push @ct, ("*" x $cur_level), " FOLDER: $name\n";
106 4         13 for (grep {!/^(ID|NAME)$/} $sorter->(keys %sfields)) {
  16         118  
107 8         21 push @ct, "- $_ :: $sfields{$_}\n";
108             }
109             }
110 4         10 $cur_level++;
111             } elsif ($sname eq 'URL') {
112 3   50     7 my $name = $sfields{NAME} // '';
113 3 50 33     5 unless ($in_trash && $exclude_trash) {
114 3         7 push @ct, ("*" x $cur_level), " URL: $name\n";
115 3         7 for (grep {!/^(ID|NAME)$/} $sorter->(keys %sfields)) {
  17         110  
116 11         23 push @ct, "- $_ :: $sfields{$_}\n";
117             }
118             }
119             } else {
120 0         0 warn "Unknown section '$sname', skipped";
121 0         0 next;
122             }
123             }
124 1         16 [200, "OK", join("", @ct)];
125             }
126              
127             $SPEC{convert_org_to_opera_bookmarks} = {
128             v => 1.1,
129             summary => 'Convert back Org to Opera bookmarks (bookmarks.adr)',
130             description => <<'_',
131              
132             This program is the counterpart for `convert-opera-bookmarks-to-org`) to turn
133             back the Org document generated by that program back to Opera bookmarks .adr
134             format. See that program for more information.
135              
136             _
137             args => {
138             input => {
139             summary => 'Org document file',
140             schema => 'str*',
141             cmdline_src => 'stdin_or_files',
142             pos => 0,
143             req => 1,
144             },
145             },
146             };
147             sub convert_org_to_opera_bookmarks {
148 1     1 1 6 my %args = @_;
149              
150 1         1 my $cur_level;
151             my @sections;
152 0         0 my @ct;
153              
154 1     1   738 no strict 'refs';
  1         2  
  1         423  
155 1         9 push @ct, "Opera Hotlist version 2.0 (generated by ".__PACKAGE__.
156 1   50     2 " version ".(${__PACKAGE__.'::VERSION'} // "dev").")\n";
157 1         1 push @ct, "Options: encoding = utf8, version=3\n\n";
158              
159 1         14 for (split /^(?=\*+ )/m, $args{input}) {
160 7         8 push @sections, $_;
161             }
162 1         2 my $prev_level;
163 1         1 my $id = 0;
164 1         2 for my $section (@sections) {
165 7 50       26 $section =~ /^(\*+) (\w+): ?(.*)/ or do {
166 0         0 warn "Unknown section, skipped: $section";
167 0         0 next;
168             };
169 7         13 my ($level, $type, $sname) = ($1, $2, $3);
170 7 50 66     21 if ($type ne 'FOLDER' && $type ne 'URL') {
171 0         0 warn "Unknown section type '$type', skipped";
172 0         0 next;
173             }
174 7 100       12 if ($type eq 'FOLDER') {
175 4         5 $level = length($level);
176 4 100 100     13 if (defined($prev_level) && $level <= $prev_level) {
177 1         3 for ($level .. $prev_level) {
178 2         3 push @ct, "-\n\n";
179             }
180             }
181 4         5 $prev_level = $level;
182             }
183 7         10 push @ct, "#$type\n";
184 7         9 push @ct, "\tID=", ++$id, "\n";
185 7         8 push @ct, "\tNAME=$sname\n";
186 7         39 my %sfields = $section =~ /^- (\w+) :: (.*)/mg;
187 7         19 for ($sorter->(keys %sfields)) {
188 19         98 push @ct, "\t$_=$sfields{$_}\n";
189             }
190 7         15 push @ct, "\n";
191             }
192 1 50       51 if (defined $prev_level) {
193 1         4 push @ct, "-\n\n" for 1..$prev_level;
194             }
195 1         17 [200, "OK", join("", @ct)];
196             }
197              
198              
199             1;
200             # ABSTRACT: Convert Opera bookmarks to Org
201              
202             __END__
203              
204             =pod
205              
206             =encoding UTF-8
207              
208             =head1 NAME
209              
210             App::ConvertOperaBookmarksToOrg - Convert Opera bookmarks to Org
211              
212             =head1 VERSION
213              
214             This document describes version 0.03 of App::ConvertOperaBookmarksToOrg (from Perl distribution App-ConvertOperaBookmarksToOrg), released on 2014-10-12.
215              
216             =head1 DESCRIPTION
217              
218             This distribution provides the following utilities:
219              
220             convert-opera-bookmarks-to-org
221             convert-org-to-opera-bookmarks
222              
223             Shorter-named scripts are also provided as aliases, respectively:
224              
225             adr2org
226             org2adr
227              
228             =head1 FUNCTIONS
229              
230              
231             =head2 convert_opera_bookmarks_to_org(%args) -> [status, msg, result, meta]
232              
233             Convert Opera bookmarks (bookmarks.adr) to Org document.
234              
235             I want to keep my Opera browser bookmarks file (C<~/.opera/bookmarks.adr>) under
236             git repository, so I can synchronize them between computers. There are a few
237             annoyances though: 1) When Opera saves bookmarks file, it remove symlinks, so
238             after I have to re-symlink the file to my git repo; 2) The ID field changes
239             sporadically, creating unnecessarily large diff and merge conflicts.
240              
241             This program (and its counterpart C<convert-org-to-opera-bookmarks>) is an
242             alternative to keeping Opera bookmarks file under git. You convert to .org file,
243             put the .org file under git, and convert back to .adr. The advantage is that the
244             ID field is removed so the diff is smaller and conflict reduced. Also, you can
245             more conveniently edit using Emacs/other Org editor.
246              
247             Another alternative to this program is to use the Opera Link service from Opera
248             to synchronize your bookmarks (and a few other stuffs) between devices. But note
249             that Opera has closed some of its services in the past.
250              
251             Arguments ('*' denotes required arguments):
252              
253             =over 4
254              
255             =item * B<exclude_trash> => I<bool>
256              
257             =item * B<input>* => I<str>
258              
259             Opera addressbook file.
260              
261             =back
262              
263             Return value:
264              
265             Returns an enveloped result (an array).
266              
267             First element (status) is an integer containing HTTP status code
268             (200 means OK, 4xx caller error, 5xx function error). Second element
269             (msg) is a string containing error message, or 'OK' if status is
270             200. Third element (result) is optional, the actual result. Fourth
271             element (meta) is called result metadata and is optional, a hash
272             that contains extra information.
273              
274             (any)
275              
276              
277             =head2 convert_org_to_opera_bookmarks(%args) -> [status, msg, result, meta]
278              
279             Convert back Org to Opera bookmarks (bookmarks.adr).
280              
281             This program is the counterpart for C<convert-opera-bookmarks-to-org>) to turn
282             back the Org document generated by that program back to Opera bookmarks .adr
283             format. See that program for more information.
284              
285             Arguments ('*' denotes required arguments):
286              
287             =over 4
288              
289             =item * B<input>* => I<str>
290              
291             Org document file.
292              
293             =back
294              
295             Return value:
296              
297             Returns an enveloped result (an array).
298              
299             First element (status) is an integer containing HTTP status code
300             (200 means OK, 4xx caller error, 5xx function error). Second element
301             (msg) is a string containing error message, or 'OK' if status is
302             200. Third element (result) is optional, the actual result. Fourth
303             element (meta) is called result metadata and is optional, a hash
304             that contains extra information.
305              
306             (any)
307              
308             =head1 HOMEPAGE
309              
310             Please visit the project's homepage at L<https://metacpan.org/release/App-ConvertOperaBookmarksToOrg>.
311              
312             =head1 SOURCE
313              
314             Source repository is at L<https://github.com/perlancar/perl-App-ConvertOperaBookmarksToOrg>.
315              
316             =head1 BUGS
317              
318             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-ConvertOperaBookmarksToOrg>
319              
320             When submitting a bug or request, please include a test-file or a
321             patch to an existing test-file that illustrates the bug or desired
322             feature.
323              
324             =head1 AUTHOR
325              
326             perlancar <perlancar@cpan.org>
327              
328             =head1 COPYRIGHT AND LICENSE
329              
330             This software is copyright (c) 2014 by perlancar@cpan.org.
331              
332             This is free software; you can redistribute it and/or modify it under
333             the same terms as the Perl 5 programming language system itself.
334              
335             =cut