| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  |  | 
| 2 |  |  |  |  |  |  | use 5.010; | 
| 3 | 12 |  |  | 12 |  | 1374 | use locale; | 
|  | 12 |  |  |  |  | 37 |  | 
| 4 | 12 |  |  | 12 |  | 55 | use utf8; | 
|  | 12 |  |  |  |  | 41 |  | 
|  | 12 |  |  |  |  | 64 |  | 
| 5 | 12 |  |  | 12 |  | 4896 | use Moo; | 
|  | 12 |  |  |  |  | 112 |  | 
|  | 12 |  |  |  |  | 86 |  | 
| 6 | 12 |  |  | 12 |  | 313 | no if $] >= 5.021_006, warnings => "locale"; | 
|  | 12 |  |  |  |  | 23 |  | 
|  | 12 |  |  |  |  | 60 |  | 
| 7 | 12 |  |  | 12 |  | 3523 | extends 'Org::Element'; | 
|  | 12 |  |  |  |  | 23 |  | 
|  | 12 |  |  |  |  | 94 |  | 
| 8 |  |  |  |  |  |  | with 'Org::ElementRole'; | 
| 9 |  |  |  |  |  |  | with 'Org::ElementRole::Inline'; | 
| 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 |  |  |  |  |  |  | my @attrs = (qw/datetime has_time event_duration recurrence is_active/); | 
| 17 |  |  |  |  |  |  | for (@attrs) { | 
| 18 |  |  |  |  |  |  | has $_ => (is => 'rw', clearer=>"clear_$_"); | 
| 19 |  |  |  |  |  |  | before $_ => sub { | 
| 20 |  |  |  |  |  |  | my $self = shift; | 
| 21 |  |  |  |  |  |  | return unless defined $self->_is_parsed; # never been parsed | 
| 22 |  |  |  |  |  |  | $self->_parse_timestamp($self->_str) | 
| 23 |  |  |  |  |  |  | unless $self->_is_parsed; # has been reset, re-set | 
| 24 |  |  |  |  |  |  | }; | 
| 25 |  |  |  |  |  |  | } | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | has _repeater => (is => 'rw'); # stores the raw repeater spec, for as_string | 
| 28 |  |  |  |  |  |  | has _warning_period => (is => 'rw'); # raw warning period spec, for as_string | 
| 29 |  |  |  |  |  |  | has _is_parsed => (is => 'rw'); | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | my $self = shift; | 
| 32 |  |  |  |  |  |  | return unless defined $self->_is_parsed; # never been parsed | 
| 33 | 1 |  |  | 1 | 1 | 978 | for (@attrs) { my $m = "clear_$_"; $self->$m } | 
| 34 | 1 | 50 |  |  |  | 5 | $self->_is_parsed(0); | 
| 35 | 1 |  |  |  |  | 4 | } | 
|  | 5 |  |  |  |  | 44 |  | 
|  | 5 |  |  |  |  | 86 |  | 
| 36 | 1 |  |  |  |  | 9 |  | 
| 37 |  |  |  |  |  |  | our @dow = (undef, qw(Mon Tue Wed Thu Fri Sat Sun)); | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | my ($self) = @_; | 
| 40 |  |  |  |  |  |  | return $self->_str if $self->_str; | 
| 41 |  |  |  |  |  |  | my $dt = $self->datetime; | 
| 42 | 18 |  |  | 18 | 1 | 8448 | my ($hour2, $min2); | 
| 43 | 18 | 50 |  |  |  | 119 | if ($self->event_duration) { | 
| 44 | 0 |  |  |  |  | 0 | my $hour = $dt->hour; | 
| 45 | 0 |  |  |  |  | 0 | my $min = $dt->minute; | 
| 46 | 0 | 0 |  |  |  | 0 | my $mins = $self->event_duration / 60; | 
| 47 | 0 |  |  |  |  | 0 | $min2 = $min + $mins; | 
| 48 | 0 |  |  |  |  | 0 | my $hours = int ($min2 / 60); | 
| 49 | 0 |  |  |  |  | 0 | $hour2 = $hour + $hours; | 
| 50 | 0 |  |  |  |  | 0 | $min2  = $min2 % 60; | 
| 51 | 0 |  |  |  |  | 0 | } | 
| 52 | 0 |  |  |  |  | 0 | join("", | 
| 53 | 0 |  |  |  |  | 0 | $self->is_active ? "<" : "[", | 
| 54 |  |  |  |  |  |  | $dt->ymd, " ", | 
| 55 | 0 | 0 |  |  |  | 0 | $dow[$dt->day_of_week], | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | $self->has_time ? ( | 
| 57 |  |  |  |  |  |  | " ", | 
| 58 |  |  |  |  |  |  | sprintf("%02d:%02d", $dt->hour, $dt->minute), | 
| 59 |  |  |  |  |  |  | defined($hour2) ? ( | 
| 60 |  |  |  |  |  |  | "-", | 
| 61 |  |  |  |  |  |  | sprintf("%02d:%02d", $hour2, $min2), | 
| 62 |  |  |  |  |  |  | ) : (), | 
| 63 |  |  |  |  |  |  | $self->_repeater ? ( | 
| 64 |  |  |  |  |  |  | " ", | 
| 65 |  |  |  |  |  |  | $self->_repeater, | 
| 66 |  |  |  |  |  |  | ) : (), | 
| 67 |  |  |  |  |  |  | $self->_warning_period ? ( | 
| 68 |  |  |  |  |  |  | " ", | 
| 69 |  |  |  |  |  |  | $self->_warning_period, | 
| 70 |  |  |  |  |  |  | ) : (), | 
| 71 |  |  |  |  |  |  | ) : (), | 
| 72 |  |  |  |  |  |  | $self->is_active ? ">" : "]", | 
| 73 |  |  |  |  |  |  | ); | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | goto \&as_string; | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | require DateTime; | 
| 80 | 0 |  |  | 0 | 1 | 0 | require DateTime::Event::Recurrence; | 
| 81 |  |  |  |  |  |  | my ($self, $str, $opts) = @_; | 
| 82 |  |  |  |  |  |  | $self->_is_parsed(undef); # to avoid deep recursion | 
| 83 |  |  |  |  |  |  | $opts //= {}; | 
| 84 | 70 |  |  | 70 |  | 7752 | $opts->{allow_event_duration} //= 1; | 
| 85 | 70 |  |  |  |  | 3965746 | $opts->{allow_repeater} //= 1; | 
| 86 | 70 |  |  |  |  | 507669 |  | 
| 87 | 70 |  |  |  |  | 219 | my $num_re = qr/\d+(?:\.\d+)?/; | 
| 88 | 70 |  | 100 |  |  | 318 |  | 
| 89 | 70 |  | 100 |  |  | 299 | my $dow_re = qr/\w{1,3} |     # common, chinese 四, english thu | 
| 90 | 70 |  | 100 |  |  | 240 | \w{2,3}\.     # french mer., german Mi. | 
| 91 |  |  |  |  |  |  | /x; | 
| 92 | 70 |  |  |  |  | 220 |  | 
| 93 |  |  |  |  |  |  | $str =~ /^(?<open_bracket> \[|<) | 
| 94 | 70 |  |  |  |  | 147 | (?<year> \d{4})-(?<mon> \d{2})-(?<day> \d{2}) | 
| 95 |  |  |  |  |  |  | (?: | 
| 96 |  |  |  |  |  |  | (?:\s* (?<dow> $dow_re) \s*)? | 
| 97 |  |  |  |  |  |  | (?:\s+ | 
| 98 | 70 | 50 |  |  |  | 2310 | (?<hour> \d{2}):(?<min> \d{2}) | 
| 99 |  |  |  |  |  |  | (?:- | 
| 100 |  |  |  |  |  |  | (?<event_duration> | 
| 101 |  |  |  |  |  |  | (?<hour2> \d{2}):(?<min2> \d{2})) | 
| 102 |  |  |  |  |  |  | )? | 
| 103 |  |  |  |  |  |  | )? | 
| 104 |  |  |  |  |  |  | (?:\s+(?<repeater> | 
| 105 |  |  |  |  |  |  | (?<repeater_prefix> \+\+|\.\+|\+) | 
| 106 |  |  |  |  |  |  | (?<repeater_interval> $num_re) | 
| 107 |  |  |  |  |  |  | (?<repeater_unit> [dwmy]) | 
| 108 |  |  |  |  |  |  | (?:\/(?<repeater_interval_max> $num_re) | 
| 109 |  |  |  |  |  |  | (?<repeater_unit_max> [dwmy]))? | 
| 110 |  |  |  |  |  |  | ) | 
| 111 |  |  |  |  |  |  | )? | 
| 112 |  |  |  |  |  |  | (?:\s+(?<warning_period> | 
| 113 |  |  |  |  |  |  | - | 
| 114 |  |  |  |  |  |  | (?<warning_period_interval> $num_re) | 
| 115 |  |  |  |  |  |  | (?<warning_period_unit> [dwmy]) | 
| 116 |  |  |  |  |  |  | ) | 
| 117 |  |  |  |  |  |  | )? | 
| 118 |  |  |  |  |  |  | )? | 
| 119 |  |  |  |  |  |  | \s* (?<close_bracket> \]|>) | 
| 120 |  |  |  |  |  |  | $/x | 
| 121 |  |  |  |  |  |  | or die "Can't parse timestamp string: $str"; | 
| 122 |  |  |  |  |  |  | # just for sanity. usually doesn't happen though because Document gives us | 
| 123 |  |  |  |  |  |  | # either "[...]" or "<...>" | 
| 124 |  |  |  |  |  |  | die "Mismatch open/close brackets in timestamp: $str" | 
| 125 |  |  |  |  |  |  | if $+{open_bracket} eq '<' && $+{close_bracket} eq ']' || | 
| 126 |  |  |  |  |  |  | $+{open_bracket} eq '[' && $+{close_bracket} eq '>'; | 
| 127 |  |  |  |  |  |  | die "Duration not allowed in timestamp: $str" | 
| 128 |  |  |  |  |  |  | if !$opts->{allow_event_duration} && $+{event_duration}; | 
| 129 |  |  |  |  |  |  | die "Repeater ($+{repeater}) not allowed in timestamp: $str" | 
| 130 |  |  |  |  |  |  | if !$opts->{allow_repeater} && $+{repeater}; | 
| 131 | 70 | 50 | 66 |  |  | 1106 |  | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 132 |  |  |  |  |  |  | $self->is_active($+{open_bracket} eq '<' ? 1:0) | 
| 133 | 70 | 100 | 100 |  |  | 318 | unless defined $self->is_active; | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 69 | 100 | 100 |  |  | 197 | if ($+{event_duration} && !defined($self->event_duration)) { | 
| 136 |  |  |  |  |  |  | $self->event_duration( | 
| 137 | 68 | 100 |  |  |  | 1418 | ($+{hour2}-$+{hour})*3600 + | 
|  |  | 50 |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | ($+{min2} -$+{min} )*60 | 
| 139 |  |  |  |  |  |  | ); | 
| 140 | 68 | 100 | 66 |  |  | 641 | } | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | my %dt_args = (year => $+{year}, month=>$+{mon}, day=>$+{day}); | 
| 143 | 3 |  |  |  |  | 77 | if (defined($+{hour})) { | 
| 144 |  |  |  |  |  |  | $dt_args{hour}   = $+{hour}; | 
| 145 |  |  |  |  |  |  | $dt_args{minute} = $+{min}; | 
| 146 |  |  |  |  |  |  | $self->has_time(1); | 
| 147 | 68 |  |  |  |  | 590 | } else { | 
| 148 | 68 | 100 |  |  |  | 372 | $self->has_time(0); | 
| 149 | 20 |  |  |  |  | 65 | } | 
| 150 | 20 |  |  |  |  | 67 | if ($self->document->time_zone) { | 
| 151 | 20 |  |  |  |  | 371 | $dt_args{time_zone} = $self->document->time_zone; | 
| 152 |  |  |  |  |  |  | } | 
| 153 | 48 |  |  |  |  | 806 | #use Data::Dump; dd \%dt_args; | 
| 154 |  |  |  |  |  |  | my $dt = DateTime->new(%dt_args); | 
| 155 | 68 | 100 |  |  |  | 636 |  | 
| 156 | 1 |  |  |  |  | 5 | if ($+{repeater} && !$self->recurrence) { | 
| 157 |  |  |  |  |  |  | my $r; | 
| 158 |  |  |  |  |  |  | my $i = $+{repeater_interval}; | 
| 159 | 68 |  |  |  |  | 418 | my $u = $+{repeater_unit}; | 
| 160 |  |  |  |  |  |  | if ($u eq 'd') { | 
| 161 | 67 | 100 | 66 |  |  | 30922 | $r = DateTime::Event::Recurrence->daily( | 
| 162 | 12 |  |  |  |  | 73 | interval=>$i, start=>$dt); | 
| 163 | 12 |  |  |  |  | 41 | } elsif ($u eq 'w') { | 
| 164 | 12 |  |  |  |  | 39 | $r = DateTime::Event::Recurrence->weekly( | 
| 165 | 12 | 100 |  |  |  | 43 | interval=>$i, start=>$dt); | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 166 | 2 |  |  |  |  | 13 | } elsif ($u eq 'm') { | 
| 167 |  |  |  |  |  |  | $r = DateTime::Event::Recurrence->monthly( | 
| 168 |  |  |  |  |  |  | interval=>$i, start=>$dt); | 
| 169 | 3 |  |  |  |  | 12 | } elsif ($u eq 'y') { | 
| 170 |  |  |  |  |  |  | $r = DateTime::Event::Recurrence->yearly( | 
| 171 |  |  |  |  |  |  | interval=>$i, start=>$dt); | 
| 172 | 4 |  |  |  |  | 12 | } else { | 
| 173 |  |  |  |  |  |  | die "BUG: Unknown repeater unit $u in timestamp $str"; | 
| 174 |  |  |  |  |  |  | } | 
| 175 | 3 |  |  |  |  | 14 | $self->recurrence($r); | 
| 176 |  |  |  |  |  |  | $self->_repeater($+{repeater}); | 
| 177 |  |  |  |  |  |  | } | 
| 178 | 0 |  |  |  |  | 0 |  | 
| 179 |  |  |  |  |  |  | if ($+{warning_period}) { | 
| 180 | 12 |  |  |  |  | 3755 | my $i = $+{warning_period_interval}; | 
| 181 | 12 |  |  |  |  | 108 | my $u = $+{warning_period_unit}; | 
| 182 |  |  |  |  |  |  | if ($u eq 'd') { | 
| 183 |  |  |  |  |  |  | } elsif ($u eq 'w') { | 
| 184 | 67 | 100 |  |  |  | 284 | } elsif ($u eq 'm') { | 
| 185 | 1 |  |  |  |  | 4 | } elsif ($u eq 'y') { | 
| 186 | 1 |  |  |  |  | 4 | } else { | 
| 187 | 1 | 50 |  |  |  | 14 | die "BUG: Unknown warning period unit $u in timestamp $str"; | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  | $self->_warning_period($+{warning_period}); | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  |  | 
| 192 | 0 |  |  |  |  | 0 | $self->_is_parsed(1); | 
| 193 |  |  |  |  |  |  | $self->datetime($dt); | 
| 194 | 1 |  |  |  |  | 9 | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | 1; | 
| 197 | 67 |  |  |  |  | 202 | # ABSTRACT: Represent Org timestamp | 
| 198 | 67 |  |  |  |  | 1282 |  | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =pod | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | =encoding UTF-8 | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | =head1 NAME | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | Org::Element::Timestamp - Represent Org timestamp | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | =head1 VERSION | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | This document describes version 0.558 of Org::Element::Timestamp (from Perl distribution Org-Parser), released on 2022-06-23. | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | Derived from L<Org::Element>. | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | Supported formats: | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | =over | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | =item * C<[...]> and C<< <...> >> (active) forms | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | =item * basic date: C<[2013-10-27 Sun]> | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | =item * event duration: C<[2011-03-23 Wed 10:12-11:23]> | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | =item * repeater: C<[2011-03-23 Wed +3m]> including C<++> and C<.+> | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | =item * habit-style repeater: C<[2011-03-23 Wed 10:12 +1d/2d]> | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | =item * warning period: C<< <2011-05-25 Wed +17.1m -13.2d> >> | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | =back | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | =head1 BUGS AND LIMITATIONS | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | =over | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | =item * Habit-style repeater (e.g. 2d/3d) is not yet represented in C<recurrence> | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | The recurrence object currently will still only include 2d (without the maximum | 
| 241 |  |  |  |  |  |  | interval). | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | =back | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | =head1 ATTRIBUTES | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | =head2 datetime => DATETIME_OBJ | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | =head2 has_time => BOOL | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | =head2 event_duration => INT | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | Event duration in seconds, e.g. for event timestamp like this: | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | <2011-03-23 10:15-13:25> | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | event_duration is 7200+600=7800 (2 hours 10 minutes). | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =head2 recurrence => DateTime::Event::Recurrence object | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =head2 is_active => BOOL | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | =head1 METHODS | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | =head2 $el->clear_parse_result | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | Clear parse result. | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | Since the DateTime::Set::ICal (recurrence) object contains coderefs (and thus | 
| 270 |  |  |  |  |  |  | poses problem to serialization), an option is provided to remove parse result. | 
| 271 |  |  |  |  |  |  | You can do this prior to serializing the object. | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | Timestamp will automatically be parsed again from _str when one of the | 
| 274 |  |  |  |  |  |  | attributes is accessed. | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | =head2 as_string => str | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | From L<Org::Element>. | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | =head2 as_text => str | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | From L<Org::ElementRole::Inline>. | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | =head1 HOMEPAGE | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | Please visit the project's homepage at L<https://metacpan.org/release/Org-Parser>. | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | =head1 SOURCE | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | Source repository is at L<https://github.com/perlancar/perl-Org-Parser>. | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | =head1 AUTHOR | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | perlancar <perlancar@cpan.org> | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | =head1 CONTRIBUTING | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | To contribute, you can send patches by email/via RT, or send pull requests on | 
| 300 |  |  |  |  |  |  | GitHub. | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | Most of the time, you don't need to build the distribution yourself. You can | 
| 303 |  |  |  |  |  |  | simply modify the code, then test via: | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | % prove -l | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | If you want to build the distribution (e.g. to try to install it locally on your | 
| 308 |  |  |  |  |  |  | system), you can install L<Dist::Zilla>, | 
| 309 |  |  |  |  |  |  | L<Dist::Zilla::PluginBundle::Author::PERLANCAR>, and sometimes one or two other | 
| 310 |  |  |  |  |  |  | Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required | 
| 311 |  |  |  |  |  |  | beyond that are considered a bug and can be reported to me. | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | This software is copyright (c) 2022, 2021, 2020, 2019, 2017, 2016, 2015, 2014, 2013, 2012, 2011 by perlancar <perlancar@cpan.org>. | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 318 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | =head1 BUGS | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Org-Parser> | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | When submitting a bug or request, please include a test-file or a | 
| 325 |  |  |  |  |  |  | patch to an existing test-file that illustrates the bug or desired | 
| 326 |  |  |  |  |  |  | feature. | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | =cut |