File Coverage

blib/lib/Org/Parser.pm
Criterion Covered Total %
statement 48 66 72.7
branch 21 32 65.6
condition 15 22 68.1
subroutine 6 6 100.0
pod 2 2 100.0
total 92 128 71.8


line stmt bran cond sub pod time code
1             package Org::Parser;
2              
3 23     23   1366357 use 5.014; # compilation failure in older perls, RT#141560
  23         235  
4 23     23   12809 use Moo;
  23         307332  
  23         146  
5              
6 23     23   45765 use Org::Document;
  23         88  
  23         1115  
7 23     23   174 use Scalar::Util qw(blessed);
  23         43  
  23         16935  
8              
9             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
10             our $DATE = '2023-07-12'; # DATE
11             our $DIST = 'Org-Parser'; # DIST
12             our $VERSION = '0.559'; # VERSION
13              
14             sub parse {
15 96     96 1 507847 my ($self, $arg, $opts) = @_;
16 96 100       406 die "Please specify a defined argument to parse()\n" unless defined($arg);
17              
18 94   100     567 $opts //= {};
19              
20 94         177 my $str;
21 94         201 my $r = ref($arg);
22 94 100 33     303 if (!$r) {
    100 66        
    100          
    100          
23 90         197 $str = $arg;
24             } elsif ($r eq 'ARRAY') {
25 1         5 $str = join "", @$arg;
26             } elsif ($r eq 'GLOB' || blessed($arg) && $arg->isa('IO::Handle')) {
27 1         30 $str = join "", <$arg>;
28             } elsif ($r eq 'CODE') {
29 1         3 my @chunks;
30 1         5 while (defined(my $chunk = $arg->())) {
31 4         23 push @chunks, $chunk;
32             }
33 1         8 $str = join "", @chunks;
34             } else {
35 1         8 die "Invalid argument, please supply a ".
36             "string|arrayref|coderef|filehandle\n";
37             }
38             Org::Document->new(
39             from_string=>$str,
40             time_zone=>$opts->{time_zone},
41             ignore_unknown_settings=>$opts->{ignore_unknown_settings},
42 93         2408 );
43             }
44              
45             sub parse_file {
46 13     13 1 4051851 require File::Slurper;
47 13         56409 my ($self, $filename, $opts) = @_;
48 13   100     86 $opts //= {};
49              
50 13         28 state $loaded;
51              
52 13         53 my $content = File::Slurper::read_text($filename);
53              
54 13         1747 my $cf = $opts->{cache_file}; # old option, new option is 'cache' (automatic setting of cache file)
55 13         41 my $doc;
56             my $cache; # undef = no caching; 0 = not cached, should cache; 1 = cached
57 13 50 33     107 if (!$cf && ($opts->{cache} // $ENV{PERL_ORG_PARSER_CACHE})) {
      66        
58 0         0 require Cwd;
59 0         0 require Digest::MD5;
60 0         0 my @dirs = ("$ENV{HOME}/.cache/perl-org-parser", $ENV{HOME});
61 0         0 my $dir;
62 0         0 for (@dirs) {
63 0 0       0 if (-d $_) { $dir = $_; last }
  0 0       0  
  0         0  
64 0         0 elsif (mkdir $_) { $dir = $_; last }
  0         0  
65             }
66 0 0       0 die "Can't find a suitable cache directory" unless $dir;
67 0 0       0 my $abs = Cwd::abs_path($filename) or die "Can't find $filename";
68 0         0 my $base = $abs; $base =~ s!.+/!!;
  0         0  
69 0         0 $cf = "$dir/$base.".Digest::MD5::md5_hex($abs).".storable";
70             }
71 13 100       48 if ($cf) {
72 3         853 require Storable;
73 3   66     4571 $cache = !!((-e $cf) && (-M $cf) <= (-M $filename));
74 3 100       17 if ($cache) {
75 1         3 eval {
76 1         4 $doc = Storable::retrieve($cf);
77 1 50       145 $doc->load_element_modules unless $loaded++;
78             };
79 1 50       25 if ($@) {
80 0         0 warn "Failed retrieving document from cache: $@, reparsing ...";
81 0         0 $cache = 0;
82             }
83             }
84             }
85              
86 13 100       87 $doc = $self->parse($content, $opts) unless $cache;
87 12 100 100     348 if (defined($cache) && !$cache) {
88 1         7 require Storable;
89 1         24 for ($doc->find('Timestamp')) {
90 0         0 $_->clear_parse_result;
91             }
92 1         6 Storable::store($doc, $cf);
93             }
94              
95 12         427 $doc;
96             }
97              
98             1;
99             # ABSTRACT: Parse Org documents
100              
101             __END__
102              
103             =pod
104              
105             =encoding UTF-8
106              
107             =head1 NAME
108              
109             Org::Parser - Parse Org documents
110              
111             =head1 VERSION
112              
113             This document describes version 0.559 of Org::Parser (from Perl distribution Org-Parser), released on 2023-07-12.
114              
115             =head1 SYNOPSIS
116              
117             use 5.010;
118             use Org::Parser;
119             my $orgp = Org::Parser->new();
120              
121             # parse a file
122             my $doc = $orgp->parse_file("$ENV{HOME}/todo.org");
123              
124             # parse a string
125             $doc = $orgp->parse(<<EOF);
126             #+TODO: TODO | DONE CANCELLED
127             <<<radio target>>>
128             * heading1a
129             ** TODO heading2a
130             SCHEDULED: <2011-03-31 Thu>
131             [[some][link]]
132             ** DONE heading2b
133             [2011-03-18 ]
134             this will become a link: radio target
135             * TODO heading1b *bold*
136             - some
137             - plain
138             - list
139             - [ ] with /checkbox/
140             * and
141             * sublist
142             * CANCELLED heading1c
143             + definition :: list
144             + another :: def
145             EOF
146              
147             # walk the document tree
148             $doc->walk(sub {
149             my ($el) = @_;
150             return unless $el->isa('Org::Element::Headline');
151             say "heading level ", $el->level, ": ", $el->title->as_string;
152             });
153              
154             will print something like:
155              
156             heading level 1: heading1a
157             heading level 2: heading2a
158             heading level 2: heading2b *bold*
159             heading level 1: heading1b
160             heading level 1: heading1c
161              
162             A command-line utility (in a separate distribution: L<App::OrgUtils>) is
163             available for debugging:
164              
165             % dump-org-structure ~/todo.org
166             Document:
167             Setting: "#+TODO: TODO | DONE CANCELLED\n"
168             RadioTarget: "<<<radio target>>>"
169             Text: "\n"
170             Headline: l=1
171             (title)
172             Text: "heading1a"
173             (children)
174             Headline: l=2 todo=TODO
175             (title)
176             Text: "heading2a"
177             (children)
178             Text: "SCHEDULED: "
179             ...
180              
181             =head1 DESCRIPTION
182              
183             This module parses Org documents. See http://orgmode.org/ for more details on
184             Org documents.
185              
186             See C<todo.org> in the distribution for the list of already- and not yet
187             implemented stuffs.
188              
189             =head1 ATTRIBUTES
190              
191             =head1 METHODS
192              
193             =head2 new()
194              
195             Create a new parser instance.
196              
197             =head2 $orgp->parse($str | $arrayref | $coderef | $filehandle, \%opts) => $doc
198              
199             Parse document (which can be contained in a scalar $str, an arrayref of lines
200             $arrayref, a subroutine which will be called for chunks until it returns undef,
201             or a filehandle).
202              
203             Returns L<Org::Document> object.
204              
205             If 'handler' attribute is specified, will call handler repeatedly during
206             parsing. See the 'handler' attribute for more details.
207              
208             Will die if there are syntax errors in documents.
209              
210             Known options:
211              
212             =over 4
213              
214             =item * time_zone => STR
215              
216             Will be passed to Org::Document's constructor.
217              
218             =back
219              
220             =head2 $orgp->parse_file($filename, \%opts) => $doc
221              
222             Just like parse(), but will load document from file instead.
223              
224             Known options (aside from those known by parse()):
225              
226             =over 4
227              
228             =item * cache => bool (default: from PERL_ORG_PARSER_CACHE, or 0)
229              
230             Since Org::Parser can spend some time to parse largish Org files, this is an
231             option to store the parse result (using L<Storable>). If caching is turned on,
232             then after the first parse, the result will be stored in:
233              
234             ~/.cache/perl-org-parser/<filename>.<md5-digest-of-file-absolute-path>.storable
235              
236             and subsequent calls to this function can directly use this cache, as long as
237             the cache is not stale.
238              
239             =back
240              
241             =head1 FAQ
242              
243             =head2 Why? Just as only perl can parse Perl, only org-mode can parse Org anyway!
244              
245             True. I'm only targetting good enough. As long as I can parse/process all my Org
246             notes and todo files, I have no complaints.
247              
248             =head2 It's too slow!
249              
250             Parser is completely regex-based at the moment (I plan to use L<Marpa> someday).
251             Performance is quite lousy but I'm not annoyed enough at the moment to overhaul
252             it.
253              
254             =head1 ENVIRONMENT
255              
256             =head2 PERL_ORG_PARSER_CACHE => bool
257              
258             Set default for C<cache> option in C<parse_file()>.
259              
260             =head1 HOMEPAGE
261              
262             Please visit the project's homepage at L<https://metacpan.org/release/Org-Parser>.
263              
264             =head1 SOURCE
265              
266             Source repository is at L<https://github.com/perlancar/perl-Org-Parser>.
267              
268             =head1 SEE ALSO
269              
270             L<Org::Document>
271              
272             =head1 AUTHOR
273              
274             perlancar <perlancar@cpan.org>
275              
276             =head1 CONTRIBUTORS
277              
278             =for stopwords Alex White Karl Williamson Steven Haryanto Tekki Trent Fisher Wong Meng Weng
279              
280             =over 4
281              
282             =item *
283              
284             Alex White <VVu@geekfarm.org>
285              
286             =item *
287              
288             Karl Williamson <khw@cpan.org>
289              
290             =item *
291              
292             Steven Haryanto <stevenharyanto@gmail.com>
293              
294             =item *
295              
296             Tekki <tekki@tekki.ch>
297              
298             =item *
299              
300             Trent Fisher <trent@cs.pdx.edu>
301              
302             =item *
303              
304             Wong Meng Weng <mengwong@pobox.com>
305              
306             =back
307              
308             =head1 CONTRIBUTING
309              
310              
311             To contribute, you can send patches by email/via RT, or send pull requests on
312             GitHub.
313              
314             Most of the time, you don't need to build the distribution yourself. You can
315             simply modify the code, then test via:
316              
317             % prove -l
318              
319             If you want to build the distribution (e.g. to try to install it locally on your
320             system), you can install L<Dist::Zilla>,
321             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
322             L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
323             Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
324             that are considered a bug and can be reported to me.
325              
326             =head1 COPYRIGHT AND LICENSE
327              
328             This software is copyright (c) 2023, 2022, 2021, 2020, 2019, 2017, 2016, 2015, 2014, 2013, 2012, 2011 by perlancar <perlancar@cpan.org>.
329              
330             This is free software; you can redistribute it and/or modify it under
331             the same terms as the Perl 5 programming language system itself.
332              
333             =head1 BUGS
334              
335             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Org-Parser>
336              
337             When submitting a bug or request, please include a test-file or a
338             patch to an existing test-file that illustrates the bug or desired
339             feature.
340              
341             =cut