File Coverage

blib/lib/Org/Element.pm
Criterion Covered Total %
statement 98 112 87.5
branch 35 50 70.0
condition 8 14 57.1
subroutine 21 22 95.4
pod 14 14 100.0
total 176 212 83.0


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