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