File Coverage

blib/lib/Org/Element.pm
Criterion Covered Total %
statement 100 114 87.7
branch 36 52 69.2
condition 9 17 52.9
subroutine 22 23 95.6
pod 15 15 100.0
total 182 221 82.3


line stmt bran cond sub pod time code
1             package Org::Element;
2              
3 24     24   13946 use 5.010;
  24         100  
4 24     24   139 use locale;
  24         55  
  24         159  
5 24     24   776 use Log::ger;
  24         48  
  24         138  
6 24     24   5077 use Moo;
  24         58  
  24         190  
7 24     24   9224 use Scalar::Util qw(refaddr);
  24         65  
  24         39991  
8              
9             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
10             our $DATE = '2023-08-05'; # DATE
11             our $DIST = 'Org-Parser'; # DIST
12             our $VERSION = '0.560'; # VERSION
13              
14             has document => (is => 'rw');
15             has parent => (is => 'rw');
16             has children => (is => 'rw');
17              
18             # store the raw string (to preserve original formatting), not all elements use
19             # this, usually only more complex elements
20             has _str => (is => 'rw');
21             has _str_include_children => (is => 'rw');
22              
23             sub die {
24 6     6 1 24 my ($self, $msg) = @_;
25 6 50 33     211 die $msg .
26             " (element: ".ref($self).
27             ", document: ".($self->document && $self->document->_srclabel ? $self->document->_srclabel : "-").")";
28             }
29              
30             sub children_as_string {
31 258     258 1 438 my ($self) = @_;
32 258 100       1475 return "" unless $self->children;
33 49         80 join "", map {$_->as_string} @{$self->children};
  67         172  
  49         104  
34             }
35              
36             sub as_string {
37 20     20 1 6312 my ($self) = @_;
38              
39 20 100       63 if (defined $self->_str) {
40 2 50       13 return $self->_str .
41             ($self->_str_include_children ? "" : $self->children_as_string);
42             } else {
43 18         40 return "" . $self->children_as_string;
44             }
45             }
46              
47             sub seniority {
48 44     44 1 1016 my ($self) = @_;
49 44         52 my $c;
50 44 50 33     249 return -4 unless $self->parent && ($c = $self->parent->children);
51 44         111 my $addr = refaddr($self);
52 44         110 for (my $i=0; $i < @$c; $i++) {
53 75 100       232 return $i if refaddr($c->[$i]) == $addr;
54             }
55 0         0 return undef; ## no critic: Subroutines::ProhibitExplicitReturnUndef
56             }
57              
58             sub prev_sibling {
59 21     21 1 44 my ($self) = @_;
60              
61 21         46 my $sen = $self->seniority;
62 21 100 66     98 return undef unless defined($sen) && $sen > 0; ## no critic: Subroutines::ProhibitExplicitReturnUndef
63 12         32 my $c = $self->parent->children;
64 12         94 $c->[$sen-1];
65             }
66              
67             sub next_sibling {
68 7     7 1 3198 my ($self) = @_;
69              
70 7         18 my $sen = $self->seniority;
71 7 50       27 return undef unless defined($sen); ## no critic: Subroutines::ProhibitExplicitReturnUndef
72 7         20 my $c = $self->parent->children;
73 7 100       31 return undef unless $sen < @$c-1; ## no critic: Subroutines::ProhibitExplicitReturnUndef
74 5         32 $c->[$sen+1];
75             }
76              
77 890     890 1 1817 sub extra_walkables { return () }
78              
79             sub walk {
80 1053     1053 1 46787 my ($self, $code, $_level) = @_;
81 1053   100     2059 $_level //= 0;
82 1053         2133 $code->($self, $_level);
83 1051 100       9646 if ($self->children) {
84             # we need to copy children first to a temporary array so that in the
85             # event when during walk a child is removed, all the children are still
86             # walked into.
87 376         475 my @children = @{ $self->children };
  376         866  
88 376         725 for (@children) {
89 805         2014 $_->walk($code, $_level+1);
90             }
91             }
92 1051         2118 $_->walk($code, $_level+1) for $self->extra_walkables;
93             }
94              
95             sub find {
96 7     7 1 2113 my ($self, $criteria) = @_;
97 7 50       32 return unless $self->children;
98 7         14 my @res;
99             $self->walk(
100             sub {
101 58     58   84 my $el = shift;
102 58 100       212 if (ref($criteria) eq 'CODE') {
    50          
103 12 100       23 push @res, $el if $criteria->($el);
104             } elsif ($criteria =~ /^\w+$/) {
105 46 100       214 push @res, $el if $el->isa("Org::Element::$criteria");
106             } else {
107 0 0       0 push @res, $el if $el->isa($criteria);
108             }
109 7         55 });
110 7         73 @res;
111             }
112              
113             sub walk_parents {
114 10     10 1 22 my ($self, $code) = @_;
115 10         29 my $parent = $self->parent;
116 10         25 while ($parent) {
117 17 100       35 return $parent unless $code->($self, $parent);
118 14         44 $parent = $parent->parent;
119             }
120 7         18 return;
121             }
122              
123             sub headline {
124 2     2 1 5 my ($self) = @_;
125 2         4 my $h;
126             $self->walk_parents(
127             sub {
128 2     2   7 my ($el, $p) = @_;
129 2 50       11 if ($p->isa('Org::Element::Headline')) {
130 2         3 $h = $p;
131 2         7 return;
132             }
133 0         0 1;
134 2         16 });
135 2         19 $h;
136             }
137              
138             sub headlines {
139 1     1 1 4 my ($self) = @_;
140 1         3 my @res;
141             $self->walk_parents(
142             sub {
143 4     4   10 my ($el, $p) = @_;
144 4 100       13 if ($p->isa('Org::Element::Headline')) {
145 3         4 push @res, $p;
146             }
147 4         9 1;
148 1         8 });
149 1         6 @res;
150             }
151              
152             sub settings {
153 0     0 1 0 my ($self, $criteria) = @_;
154              
155 0         0 my @settings = grep { $_->isa("Org::Element::Setting") }
156 0         0 @{ $self->children };
  0         0  
157 0 0       0 if ($criteria) {
158 0 0       0 if (ref $criteria eq 'CODE') {
159 0         0 @settings = grep { $criteria->($_) } @settings;
  0         0  
160             } else {
161 0         0 @settings = grep { $_->name eq $criteria } @settings;
  0         0  
162             }
163             }
164 0         0 @settings;
165             }
166              
167             sub field_name {
168 5     5 1 22 my ($self) = @_;
169              
170 5         15 my $prev = $self->prev_sibling;
171 5 50 33     30 if ($prev && $prev->isa('Org::Element::Text')) {
172 5         18 my $text = $prev->as_string;
173 5 100       33 if ($text =~ /(?:\A|\R)\s*(.+?)\s*:\s*\z/) {
174 3         21 return $1;
175             }
176             }
177 2         6 my $parent = $self->parent;
178 2 100 66     13 if ($parent && $parent->isa('Org::Element::ListItem')) {
179 1         8 my $list = $parent->parent;
180 1 50       6 if ($list->type eq 'D') {
181 1         6 return $parent->desc_term->as_string;
182             }
183             }
184             # TODO
185             #if ($parent && $parent->isa('Org::Element::Drawer') &&
186             # $parent->name eq 'PROPERTIES') {
187             #}
188 1         5 return;
189             }
190              
191             sub remove {
192 1     1 1 839 my ($self) = @_;
193 1         5 my $parent = $self->parent;
194 1 50       4 return unless $parent;
195 1         2 splice @{$parent->children}, $self->seniority, 1;
  1         14  
196             }
197              
198             1;
199             # ABSTRACT: Base class for Org document elements
200              
201             __END__
202              
203             =pod
204              
205             =encoding UTF-8
206              
207             =head1 NAME
208              
209             Org::Element - Base class for Org document elements
210              
211             =head1 VERSION
212              
213             This document describes version 0.560 of Org::Element (from Perl distribution Org-Parser), released on 2023-08-05.
214              
215             =head1 SYNOPSIS
216              
217             # Don't use directly, use the other Org::Element::* classes.
218              
219             =head1 DESCRIPTION
220              
221             This is the base class for all the other Org element classes.
222              
223             =head1 ATTRIBUTES
224              
225             =head2 document => DOCUMENT
226              
227             Link to document object. Elements need this to access file-wide settings,
228             properties, etc.
229              
230             =head2 parent => undef | ELEMENT
231              
232             Link to parent element. Undef if this element is the root element.
233              
234             =head2 children => undef | ARRAY_OF_ELEMENTS
235              
236             =head1 METHODS
237              
238             =head2 $el->children_as_string() => STR
239              
240             Return a concatenation of children's as_string(), or "" if there are no
241             children.
242              
243             =head2 $el->as_string() => STR
244              
245             Return the string representation of element. The default implementation will
246             just use _str (if defined) concatenated with children_as_string().
247              
248             =head2 $el->seniority => INT
249              
250             Find out the ranking of brothers/sisters of all sibling. If we are the first
251             child of parent, return 0. If we are the second child, return 1, and so on.
252              
253             =head2 $el->prev_sibling() => ELEMENT | undef
254              
255             =head2 $el->next_sibling() => ELEMENT | undef
256              
257             =head2 $el->extra_walkables => LIST
258              
259             Return extra walkable elements. The default is to return an empty list, but some
260             elements can have this, for L<Org::Element::Headline>'s title is also a walkable
261             element.
262              
263             =head2 $el->walk(CODEREF)
264              
265             Call CODEREF for node and all descendent nodes (and extra walkables),
266             depth-first. Code will be given the element object as argument.
267              
268             =head2 $el->find(CRITERIA) => ELEMENTS
269              
270             Find subelements. CRITERIA can be a word (e.g. 'Headline' meaning of class
271             'Org::Element::Headline') or a class name ('Org::Element::ListItem') or a
272             coderef (which will be given the element to test). Will return matched elements.
273              
274             =head2 $el->walk_parents(CODE)
275              
276             Run CODEREF for parent, and its parent, and so on until the root element (the
277             document), or until CODEREF returns a false value. CODEREF will be supplied
278             ($el, $parent). Will return the last parent walked.
279              
280             =head2 $el->headline() => ELEMENT
281              
282             Get current headline. Return undef if element is not under any headline.
283              
284             =head2 $el->headlines() => ELEMENTS
285              
286             Get current headline (in the first element of the result list), its parent, its
287             parent's parent, and so on until the topmost headline. Return empty list if
288             element is not under any headline.
289              
290             =head2 $el->settings(CRITERIA) => ELEMENTS
291              
292             Get L<Org::Element::Setting> nodes directly under the element. Equivalent to:
293              
294             my @settings = grep { $_->isa("Org::Element::Setting") } @{ $el->children };
295              
296             If CRITERIA is specified, will filter based on some criteria. CRITERIA can be a
297             coderef, or a string to filter by setting's name, example:
298              
299             my ($doc_title) = $doc->settings('TITLE');
300              
301             Take note of the list operator on the left because C<settings()> return a list.
302              
303             =head2 $el->field_name() => STR
304              
305             Try to extract "field name", being defined as either some text on the left side:
306              
307             DEADLINE: <2011-06-09 >
308              
309             or a description term in a description list:
310              
311             - wedding anniversary :: <2011-06-10 >
312              
313             =head2 $el->remove()
314              
315             Remove element from the tree. Basically just remove the element from its parent.
316              
317             =head2 $el->die(STR)
318              
319             Utility method to format C<die> message.
320              
321             =head1 HOMEPAGE
322              
323             Please visit the project's homepage at L<https://metacpan.org/release/Org-Parser>.
324              
325             =head1 SOURCE
326              
327             Source repository is at L<https://github.com/perlancar/perl-Org-Parser>.
328              
329             =head1 AUTHOR
330              
331             perlancar <perlancar@cpan.org>
332              
333             =head1 CONTRIBUTING
334              
335              
336             To contribute, you can send patches by email/via RT, or send pull requests on
337             GitHub.
338              
339             Most of the time, you don't need to build the distribution yourself. You can
340             simply modify the code, then test via:
341              
342             % prove -l
343              
344             If you want to build the distribution (e.g. to try to install it locally on your
345             system), you can install L<Dist::Zilla>,
346             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
347             L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
348             Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
349             that are considered a bug and can be reported to me.
350              
351             =head1 COPYRIGHT AND LICENSE
352              
353             This software is copyright (c) 2023, 2022, 2021, 2020, 2019, 2017, 2016, 2015, 2014, 2013, 2012, 2011 by perlancar <perlancar@cpan.org>.
354              
355             This is free software; you can redistribute it and/or modify it under
356             the same terms as the Perl 5 programming language system itself.
357              
358             =head1 BUGS
359              
360             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Org-Parser>
361              
362             When submitting a bug or request, please include a test-file or a
363             patch to an existing test-file that illustrates the bug or desired
364             feature.
365              
366             =cut