File Coverage

blib/lib/Org/Element/Headline.pm
Criterion Covered Total %
statement 167 169 98.8
branch 70 98 71.4
condition 40 51 78.4
subroutine 21 22 95.4
pod 12 14 85.7
total 310 354 87.5


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 16     16   673 use locale;
  16         50  
4 16     16   77 use Log::ger;
  16         24  
  16         79  
5 16     16   456 use Moo;
  16         24  
  16         105  
6 16     16   3130 use experimental 'smartmatch';
  16         35  
  16         118  
7 16     16   5040 extends 'Org::Element';
  16         30  
  16         109  
8             with 'Org::ElementRole';
9             with 'Org::ElementRole::Block';
10              
11             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
12             our $DATE = '2022-06-23'; # DATE
13             our $DIST = 'Org-Parser'; # DIST
14             our $VERSION = '0.558'; # VERSION
15              
16             has level => (is => 'rw');
17             has title => (is => 'rw');
18             has priority => (is => 'rw');
19             has tags => (is => 'rw');
20             has is_todo => (is => 'rw');
21             has is_done => (is => 'rw');
22             has todo_state => (is => 'rw');
23             has statistics_cookie => (is => 'rw');
24              
25             # old name, deprecated since 2014-07-17, will be removed in the future
26              
27 0     0 0 0 my $self = shift;
28             grep {defined} ($self->title);
29             }
30 161     161 1 228  
31 161         269 my ($self) = @_;
  161         520  
32             return $self->_str if defined $self->_str;
33             join("",
34             "*" x $self->level,
35 24     24 0 31 " ",
36 24 100       70 $self->is_todo ? $self->todo_state." " : "",
37             $self->priority ? "[#".$self->priority."] " : "",
38             $self->statistics_cookie ? "[".$self->statistics_cookie."] " : "",
39             $self->title->as_string,
40             $self->tags && @{$self->tags} ?
41             " :".join(":", @{$self->tags}).":" : "",
42             "\n");
43             }
44              
45 1 50 33     15 my ($self) = @_;
  0 50       0  
    50          
    50          
46             $self->header_as_string . $self->children_as_string;
47             }
48              
49             my ($self, $name) = @_;
50 24     24 1 819 my @res = @{ $self->tags // [] };
51 24         33 $self->walk_parents(
52             sub {
53             my ($el, $parent) = @_;
54             return 1 unless $parent->isa('Org::Element::Headline');
55 6     6 1 3924 if ($parent->tags) {
56 6   100     11 for (@{ $parent->tags }) {
  6         27  
57             push @res, $_ unless $_ ~~ @res;
58             }
59 7     7   11 }
60 7 100       36 1;
61 1 50       4 });
62 1         3 for (@{ $self->document->tags }) {
  1         3  
63 1 50       4 push @res, $_ unless $_ ~~ @res;
64             }
65             @res;
66 1         2 }
67 6         42  
68 6         24 my ($self) = @_;
  6         17  
69 6 50       29  
70             for my $s ($self->title, $self) {
71 6         28 my $ats;
72             $s->walk(
73             sub {
74             my ($el) = @_;
75 5     5 1 741 return if $ats;
76             $ats = $el if $el->isa('Org::Element::Timestamp') &&
77 5         14 $el->is_active;
78 8         9 }
79             );
80             return $ats if $ats;
81 24     24   32 }
82 24 100       36 return;
83 19 100 100     157 }
84              
85             my ($self) = @_;
86 8         37  
87 8 100       40 return 1 unless $self->children;
88              
89 2         10 my $res;
90             for my $child (@{ $self->children }) {
91             $child->walk(
92             sub {
93 4     4 1 739 return if defined($res);
94             my ($el) = @_;
95 4 100       20 if ($el->isa('Org::Element::Headline')) {
96             $res = 0;
97 2         3 goto EXIT_WALK;
98 2         3 }
  2         5  
99             }
100             );
101 2 50   2   6 }
102 2         4 EXIT_WALK:
103 2 50       9 $res //= 1;
104 2         3 $res;
105 2         16 }
106              
107             my ($self, $num_levels) = @_;
108 2         12 $num_levels //= 1;
109             return if $num_levels == 0;
110             die "Please specify a positive number of levels" if $num_levels < 0;
111 2   50     9  
112 2         10 for my $i (1..$num_levels) {
113              
114             my $l = $self->level;
115             last if $l <= 1;
116 8     8 1 2824 $l--;
117 8   100     30 $self->level($l);
118 8 50       14  
119 8 50       13 $self->_str(undef);
120              
121 8         15 my $parent = $self->parent;
122             my $siblings = $parent->children;
123 9         18 my $pos = $self->seniority;
124 9 100       16  
125 8         9 # our children stay as children
126 8         15  
127             # our right sibling headline(s) become children
128 8         16 while (1) {
129             my $s = $siblings->[$pos+1];
130 8         14 last unless $s && $s->isa('Org::Element::Headline')
131 8         10 && $s->level > $l;
132 8         26 $self->children([]) unless defined $self->children;
133             push @{$self->children}, $s;
134             splice @$siblings, $pos+1, 1;
135             $s->parent($self);
136             }
137 8         10  
138 11         18 # our parent headline can become sibling if level is the same
139 11 100 66     37 if ($parent->isa('Org::Element::Headline') && $parent->level == $l) {
      100        
140             splice @$siblings, $pos, 1;
141 3 100       11 my $gparent = $parent->parent;
142 3         4 splice @{$gparent->children}, $parent->seniority+1, 0, $self;
  3         6  
143 3         7 $self->parent($gparent);
144 3         5 }
145              
146             }
147             }
148 8 100 100     34  
149 2         3 my ($self, $num_levels) = @_;
150 2         5 $num_levels //= 1;
151 2         3 return if $num_levels == 0;
  2         7  
152 2         9 die "Please specify a positive number of levels" if $num_levels < 0;
153              
154             for my $i (1..$num_levels) {
155              
156             my $l = $self->level;
157             $l++;
158             $self->level($l);
159 6     6 1 1459  
160 6   100     20 $self->_str(undef);
161 6 50       12  
162 6 50       12 # prev sibling can become parent
163             my $ps = $self->prev_sibling;
164 6         11 if ($ps && $ps->isa('Org::Element::Headline') && $ps->level < $l) {
165             splice @{$self->parent->children}, $self->seniority, 1;
166 8         14 $ps->children([]) if !defined($ps->children);
167 8         10 push @{$ps->children}, $self;
168 8         11 $self->parent($ps);
169             }
170 8         15  
171             }
172             }
173 8         14  
174 8 100 66     31 my ($self, $num_levels) = @_;
      100        
175 1         3 $num_levels //= 1;
  1         5  
176 1 50       9 return if $num_levels == 0;
177 1         2 die "Please specify a positive number of levels" if $num_levels < 0;
  1         3  
178 1         4  
179             for my $i (1..$num_levels) {
180             last if $self->level <= 1;
181             $_->promote_node() for $self->find('Headline');
182             }
183             }
184              
185 1     1 1 730 my ($self, $num_levels) = @_;
186 1   50     7 $num_levels //= 1;
187 1 50       4 return if $num_levels == 0;
188 1 50       4 die "Please specify a positive number of levels" if $num_levels < 0;
189              
190 1         5 for my $i (1..$num_levels) {
191 1 50       5 $_->demote_node() for $self->find('Headline');
192 1         9 }
193             }
194              
195             my $self = shift;
196             my $wanted_drawer_name = shift || "PROPERTIES";
197 1     1 1 754  
198 1   50     8 for my $d (@{$self->children||[]}) {
199 1 50       3 log_trace("seeking $wanted_drawer_name drawer in child: %s (%s)", $d->as_string, ref($d));
200 1 50       3 next unless ($d->isa('Org::Element::Drawer')
201             && $d->name eq $wanted_drawer_name
202 1         4 && $d->properties);
203 1         5 return $d;
204             }
205             }
206              
207             my ($self, $name, $search_parent, $search_docprop) = @_;
208 13     13 1 16 #$log->tracef("-> get_property(%s, search_par=%s)", $name, $search_parent);
209 13   50     24 my $parent = $self->parent;
210              
211 13 50       15 my $propd = $self->get_drawer("PROPERTIES");
  13         74  
212 28         68 return $propd->properties->{$name} if
213 28 100 100     164 $propd && defined $propd->properties->{$name};
      66        
214              
215             if ($parent && $search_parent) {
216 13         25 while ($parent) {
217             if ($parent->isa('Org::Element::Headline')) {
218             my $res = $parent->get_property($name, 0, 0);
219             return $res if defined $res;
220             }
221 12     12 1 850 $parent = $parent->parent;
222             }
223 12         27 }
224              
225 12         20 if ($search_docprop // 1) {
226             log_trace("Getting property from document's .properties");
227 12 100 66     79 return $self->document->properties->{$name};
228             }
229 7 100 66     20 undef;
230 2         5 }
231 3 50       7  
232 3         7 my $self = shift;
233 3 100       14  
234             my $statc = $self->statistics_cookie;
235 1         3 return unless $statc;
236              
237             my $num_done = 0;
238             my $num_total = 0;
239 5 100 100     14  
240 4         10 # count using checks on first-level list's children, or from first-level
241 4         43 # subheadlines
242             for my $chld (@{ $self->children // [] }) {
243 1         1 if ($chld->isa("Org::Element::Headline")) {
244             for my $el (@{ $self->children }) {
245             next unless $el->isa("Org::Element::Headline");
246             if ($el->is_todo) {
247 8     8 1 745 $num_total++;
248             $num_done++ if $el->is_done;
249 8         12 }
250 8 100       15 }
251             last;
252 4         5 } elsif ($chld->isa("Org::Element::List")) {
253 4         5 for my $el (@{ $self->children }) {
254             next unless $el->isa("Org::Element::List");
255             for my $el2 (@{ $el->children }) {
256             next unless $el2->isa("Org::Element::ListItem");
257 4   100     5 my $state = $el2->check_state;
  4         15  
258 2 100       10 if (defined $state) {
    50          
259 1         2 $num_total++;
  1         3  
260 3 50       8 $num_done++ if $state eq 'X';
261 3 50       9 }
262 3         4 }
263 3 100       8 }
264             last;
265             }
266 1         2 }
267              
268 1         2 undef $self->{_str}; # we modify content
  1         4  
269 1 50       5 if ($statc =~ /%/) {
270 1         2 $self->statistics_cookie(
  1         4  
271 3 50       9 sprintf("%d%%", $num_total == 0 ? 0 : $num_done/$num_total * 100));
272 3         5 } else {
273 3 50       7 $self->statistics_cookie(sprintf("%d/%d", $num_done, $num_total));
274 3         4 }
275 3 100       7 }
276              
277             1;
278             # ABSTRACT: Represent Org headline
279 1         1  
280              
281             =pod
282              
283 4         9 =encoding UTF-8
284 4 100       10  
285 1 50       7 =head1 NAME
286              
287             Org::Element::Headline - Represent Org headline
288 3         14  
289             =head1 VERSION
290              
291             This document describes version 0.558 of Org::Element::Headline (from Perl distribution Org-Parser), released on 2022-06-23.
292              
293             =head1 DESCRIPTION
294              
295             Derived from L<Org::Element>.
296              
297             =for Pod::Coverage ^(header_as_string|as_string|todo_priority)$
298              
299             =head1 ATTRIBUTES
300              
301             =head2 level => INT
302              
303             Level of headline (e.g. 1, 2, 3). Corresponds to the number of bullet stars.
304              
305             =head2 title => OBJ
306              
307             L<Org::Element::Text> representing the headline title
308              
309             =head2 priority => STR
310              
311             String (optional) representing priority.
312              
313             =head2 tags => ARRAY
314              
315             Arrayref (optional) containing list of defined tags.
316              
317             =head2 is_todo => BOOL
318              
319             Whether this headline is a TODO item.
320              
321             =head2 is_done => BOOL
322              
323             Whether this TODO item is in a done state (state which requires no more action,
324             e.g. DONE). Only meaningful if headline is a TODO item.
325              
326             =head2 todo_state => STR
327              
328             TODO state.
329              
330             =head2 statistics_cookie => STR
331              
332             Statistics cookie, e.g. '5/10' or '50%'. TODO: there might be more than one
333             statistics cookie.
334              
335             =head1 METHODS
336              
337             =head2 $el->get_tags() => ARRAY
338              
339             Get tags for this headline. A headline can define tags or inherit tags from its
340             parent headline (or from document).
341              
342             =head2 $el->get_active_timestamp() => ELEMENT
343              
344             Get the first active timestamp element for this headline, either in the title or
345             in the child elements.
346              
347             =head2 $el->is_leaf() => BOOL
348              
349             Returns true if element doesn't contain subtrees.
350              
351             =head2 $el->promote_node([$num_levels])
352              
353             Promote (decrease the level) of this headline node. $level specifies number of
354             levels, defaults to 1. Won't further promote if already at level 1.
355             Illustration:
356              
357             * h1
358             ** h2 <-- promote 1 level
359             *** h3
360             *** h3b
361             ** h4
362             * h5
363              
364             becomes:
365              
366             * h1
367             * h2
368             *** h3
369             *** h3b
370             ** h4
371             * h5
372              
373             =head2 $el->demote_node([$num_levels])
374              
375             Does the opposite of promote_node().
376              
377             =head2 $el->promote_branch([$num_levels])
378              
379             Like promote_node(), but all children headlines will also be promoted.
380             Illustration:
381              
382             * h1
383             ** h2 <-- promote 1 level
384             *** h3
385             **** grandkid
386             *** h3b
387              
388             ** h4
389             * h5
390              
391             becomes:
392              
393             * h1
394             * h2
395             ** h3
396             *** grandkid
397             ** h3b
398              
399             ** h4
400             * h5
401              
402             =head2 $el->demote_branch([$num_levels])
403              
404             Does the opposite of promote_branch().
405              
406             =head2 $el->get_property($name, $search_parent) => VALUE
407              
408             Search for property named $name in the PROPERTIES drawer. If $search_parent is
409             set to true (default is false), will also search in upper-level properties
410             (useful for searching for inherited property, like foo_ALL). Return undef if
411             property cannot be found.
412              
413             Regardless of $search_parent setting, file-wide properties will be consulted if
414             property is not found in the headline's properties drawer.
415              
416             =head2 $el->get_drawer([$drawer_name]) => VALUE
417              
418             Return an entire drawer as an Org::Element::Drawer object. By default, return the
419             PROPERTIES drawer. If you want LOGBOOK or some other drawer, ask for it by name.
420              
421             =head2 $el->update_statistics_cookie
422              
423             Update the statistics cookies by recalculating the number of TODO and
424             checkboxes.
425              
426             Will do nothing if the headline does not have any statistics cookie.
427              
428             =head1 HOMEPAGE
429              
430             Please visit the project's homepage at L<https://metacpan.org/release/Org-Parser>.
431              
432             =head1 SOURCE
433              
434             Source repository is at L<https://github.com/perlancar/perl-Org-Parser>.
435              
436             =head1 AUTHOR
437              
438             perlancar <perlancar@cpan.org>
439              
440             =head1 CONTRIBUTING
441              
442              
443             To contribute, you can send patches by email/via RT, or send pull requests on
444             GitHub.
445              
446             Most of the time, you don't need to build the distribution yourself. You can
447             simply modify the code, then test via:
448              
449             % prove -l
450              
451             If you want to build the distribution (e.g. to try to install it locally on your
452             system), you can install L<Dist::Zilla>,
453             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
454             Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required
455             beyond that are considered a bug and can be reported to me.
456              
457             =head1 COPYRIGHT AND LICENSE
458              
459             This software is copyright (c) 2022, 2021, 2020, 2019, 2017, 2016, 2015, 2014, 2013, 2012, 2011 by perlancar <perlancar@cpan.org>.
460              
461             This is free software; you can redistribute it and/or modify it under
462             the same terms as the Perl 5 programming language system itself.
463              
464             =head1 BUGS
465              
466             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Org-Parser>
467              
468             When submitting a bug or request, please include a test-file or a
469             patch to an existing test-file that illustrates the bug or desired
470             feature.
471              
472             =cut