├── weaver.ini ├── .gitignore ├── t ├── data │ ├── unicode │ │ ├── latin1.org │ │ ├── latin2.org │ │ ├── fr.org │ │ ├── hr.org │ │ └── zh.org │ ├── todo-setting-after-todos.org │ ├── custom_todo_kw.org │ ├── various.org │ └── listitem.org ├── unicode.t ├── regression-rt68443.t ├── various.t ├── comment.t ├── fixed_width_section.t ├── document.t ├── radio_target.t ├── base_element-field_name.t ├── footnote.t ├── setting-todo.t ├── timerange.t ├── block.t ├── headline-get_property.t ├── link_and_target.t ├── testlib.pl ├── list.t ├── table.t ├── rt98375.t ├── 01-basics.t ├── drawer.t ├── setting.t ├── text.t ├── base_element.t ├── timestamp.t └── headline.t ├── lib └── Org │ ├── ElementRole.pm │ ├── Element │ ├── TableCell.pm │ ├── Comment.pm │ ├── TableHLine.pm │ ├── Target.pm │ ├── TimeRange.pm │ ├── RadioTarget.pm │ ├── ListItem.pm │ ├── List.pm │ ├── Link.pm │ ├── TableRow.pm │ ├── FixedWidthSection.pm │ ├── Footnote.pm │ ├── Block.pm │ ├── Drawer.pm │ ├── Text.pm │ ├── Table.pm │ ├── Setting.pm │ ├── Timestamp.pm │ └── Headline.pm │ ├── ElementRole │ ├── Block.pm │ └── Inline.pm │ ├── Parser.pm │ ├── Element.pm │ └── Document.pm ├── devnotes.org ├── dist.ini ├── todo.org └── Changes /weaver.ini: -------------------------------------------------------------------------------- 1 | [@Author::PERLANCAR::NoRinci] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Org-Parser-* 2 | .build 3 | *~ 4 | nytprof* 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /t/data/unicode/latin1.org: -------------------------------------------------------------------------------- 1 | TODO -*- mode: org; coding: utf-8; -*- 2 | 3 | * TODO rodjendän 4 | -------------------------------------------------------------------------------- /t/data/unicode/latin2.org: -------------------------------------------------------------------------------- 1 | TODO -*- mode: org; coding: utf-8; -*- 2 | 3 | * TODO rođendan 4 | -------------------------------------------------------------------------------- /t/data/unicode/fr.org: -------------------------------------------------------------------------------- 1 | TODO -*- mode: org; coding: utf-8; -*- 2 | 3 | * TODO dîner avec Frédéric 4 | -------------------------------------------------------------------------------- /t/data/unicode/hr.org: -------------------------------------------------------------------------------- 1 | TODO -*- mode: org; coding: utf-8; -*- 2 | 3 | * date test <2012-01-26 Čet 10:00> 4 | -------------------------------------------------------------------------------- /t/data/unicode/zh.org: -------------------------------------------------------------------------------- 1 | TODO -*- mode: org; coding: utf-8; -*- 2 | 3 | * date test <2012-01-26 星期五 10:00> 4 | -------------------------------------------------------------------------------- /lib/Org/ElementRole.pm: -------------------------------------------------------------------------------- 1 | package Org::ElementRole; 2 | 3 | use strict; 4 | use Moo::Role; 5 | 6 | # AUTHORITY 7 | # DATE 8 | # DIST 9 | # VERSION 10 | 11 | 1; 12 | # ABSTRACT: Role for Org::Element::* classes 13 | -------------------------------------------------------------------------------- /devnotes.org: -------------------------------------------------------------------------------- 1 | 2 | - log :: 3 | + [2023-08-05 Sat] :: "Build with PERL_POD_WEAVER_PLUGIN_RINCI_FORCE_RELOAD 4 | set to 0 since reloading Org/Document.pm will result in Moo error." this no 5 | longer works, so currently I use the Pod::Weaver bundle 6 | Author::PERLANCAR::NoRinci. 7 | -------------------------------------------------------------------------------- /t/unicode.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::Exception; 12 | use Test::More 0.96; 13 | 14 | for (glob "t/data/unicode/*.org") { 15 | lives_ok { Org::Parser->parse_file($_) } "can parse $_"; 16 | } 17 | 18 | done_testing(); 19 | -------------------------------------------------------------------------------- /lib/Org/Element/TableCell.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::TableCell; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | 8 | # AUTHORITY 9 | # DATE 10 | # DIST 11 | # VERSION 12 | 13 | 1; 14 | # ABSTRACT: Represent Org table cell 15 | 16 | =head1 DESCRIPTION 17 | 18 | Derived from L. 19 | 20 | 21 | =head1 ATTRIBUTES 22 | 23 | 24 | =head1 METHODS 25 | 26 | =cut 27 | -------------------------------------------------------------------------------- /t/data/todo-setting-after-todos.org: -------------------------------------------------------------------------------- 1 | todo keywords can be set after the todos themselves. this means we must scan the 2 | '#+TODO' settings first in the first pass, then re-scan the document to parse 3 | the elements. to scan the '#+TODO' settings we can simply use a regex since the 4 | settings must be put at the beginning of lines. 5 | * FOO foo 6 | * BAR bar 7 | * BAZ baz 8 | * TODO blah 9 | #+TODO: FOO | BAR BAZ 10 | -------------------------------------------------------------------------------- /lib/Org/ElementRole/Block.pm: -------------------------------------------------------------------------------- 1 | package Org::ElementRole::Block; 2 | 3 | use 5.010; 4 | use Moo::Role; 5 | 6 | sub is_block { 1 } 7 | 8 | sub is_inline { 0 } 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | 1; 16 | # ABSTRACT: Role for block elements 17 | 18 | =head1 DESCRIPTION 19 | 20 | 21 | =head1 REQUIRES 22 | 23 | 24 | =head1 METHODS 25 | 26 | =head2 is_block => bool (1) 27 | 28 | =head2 is_inline => bool (0) 29 | -------------------------------------------------------------------------------- /lib/Org/Element/Comment.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::Comment; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | with 'Org::ElementRole'; 8 | with 'Org::ElementRole::Block'; 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | 1; 16 | # ABSTRACT: Represent Org comment 17 | 18 | =head1 DESCRIPTION 19 | 20 | Derived from L. 21 | 22 | 23 | =head1 ATTRIBUTES 24 | 25 | 26 | =head1 METHODS 27 | 28 | =cut 29 | -------------------------------------------------------------------------------- /lib/Org/Element/TableHLine.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::TableHLine; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | 8 | # AUTHORITY 9 | # DATE 10 | # DIST 11 | # VERSION 12 | 13 | sub as_string { 14 | my ($self) = @_; 15 | return $self->_str if $self->_str; 16 | "|---\n"; 17 | } 18 | 19 | 1; 20 | #ABSTRACT: Represent Org table horizontal line 21 | 22 | =head1 DESCRIPTION 23 | 24 | Derived from L. 25 | 26 | 27 | =head1 ATTRIBUTES 28 | 29 | 30 | =head1 METHODS 31 | 32 | =for Pod::Coverage as_string 33 | 34 | =cut 35 | -------------------------------------------------------------------------------- /t/regression-rt68443.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'regression test RT#68443', 16 | filter_elements => 'Org::Element::Table', 17 | doc => <<'_', 18 | * test 19 | | some text in a table | column | 20 | |----------------------+--------| 21 | | | | 22 | something outside a table 23 | _ 24 | num => 1, 25 | ); 26 | 27 | done_testing(); 28 | -------------------------------------------------------------------------------- /t/data/custom_todo_kw.org: -------------------------------------------------------------------------------- 1 | #+TODO: TODO AA BB | FIXED CC 2 | 3 | * These are todo items (red) 4 | ** TODO ID=1; RES=todo; NOTE=default and defined above 5 | ** AA ID=2; RES=todo; NOTE=defined above 6 | ** BB ID=3; RES=todo; NOTE=defined above 7 | ** ab ID=4; RES=todo; NOTE=defined below 8 | 9 | * These are done todo items (green) 10 | ** FIXED ID=5; RES=todo,done; NOTE=defined after vertical bar 11 | ** CC ID=6; RES=todo,done; NOTE=defined after vertical bar 12 | ** ac ID=7; RES=todo,done; NOTE=last keyword defined is assumed a done state 13 | 14 | * These are not todo items 15 | ** DONE ID=8; RES=; NOTE=default but not defined 16 | ** Bb ID=9; RES=; NOTE=different case 17 | ** Cc ID=10; RES=; NOTE=different case 18 | 19 | #+TODO: ab ac 20 | -------------------------------------------------------------------------------- /t/data/various.org: -------------------------------------------------------------------------------- 1 | # this document will contain various elements 2 | 3 | * heading 4 | ** heading1a's text contains timestamps :tag1:tag2: 5 | timestamp <2011-03-23 Wed> 6 | 7 | inactive timestamp [2011-03-23 Wed 11:55] 8 | 9 | some text 10 | with newlines 11 | and something :resembling:tag: 12 | :and: property 13 | 14 | *** heading2a 15 | *** heading2b 16 | ** heading1b contains inactive timestamp [2011-03-23 Wed] 17 | ** heading1c contains active timestamp <2011-03-23 Wed> 18 | ** TODO heading1d 19 | <2011-03-23 Wed 23:23> 20 | ** DONE heading1e 21 | 22 | * properties 23 | :PROPERTIES: 24 | :a: 1 25 | :b: 2 26 | :END: 27 | ** subdrawer 28 | :PROPERTIES: 29 | :a: 1b 30 | :b: 2b 31 | :END: 32 | -------------------------------------------------------------------------------- /lib/Org/Element/Target.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::Target; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | with 'Org::ElementRole'; 8 | with 'Org::ElementRole::Inline'; 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | has target => (is => 'rw'); 16 | 17 | sub as_string { 18 | my ($self) = @_; 19 | join("", 20 | "<<", ($self->target // ""), ">>"); 21 | } 22 | 23 | sub as_text { 24 | goto \&as_string; 25 | } 26 | 27 | 1; 28 | # ABSTRACT: Represent Org target 29 | 30 | =head1 DESCRIPTION 31 | 32 | Derived from L. 33 | 34 | 35 | =head1 ATTRIBUTES 36 | 37 | =head2 target 38 | 39 | 40 | =head1 METHODS 41 | 42 | =head2 as_string => str 43 | 44 | From L. 45 | 46 | =head2 as_text => str 47 | 48 | From L. 49 | 50 | =cut 51 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | ;--------------------------------- 2 | author = perlancar 3 | copyright_holder = perlancar 4 | license = Perl_5 5 | ;--------------------------------- 6 | 7 | version = 0.561 8 | 9 | name = Org-Parser 10 | 11 | [@Author::PERLANCAR] 12 | :version=0.606 13 | 14 | [Prereqs / TestRequires] 15 | lib=0 16 | File::Temp=0.2307 17 | FindBin=0 18 | Module::Loaded=0 19 | Org::Dump=0.551 20 | Test::Exception=0 21 | Test::More=0.98 22 | 23 | [Prereqs] 24 | perl= 5.014 25 | if=0 26 | locale=0 27 | strict=0 28 | utf8=0 29 | warnings=0 30 | Cwd=0 31 | DateTime= 0 32 | DateTime::Event::Recurrence=0 33 | Digest::MD5=0 34 | File::Slurper=0 35 | List::MoreUtils=0 36 | Log::ger=0.038 37 | Module::List=0 38 | Module::Load=0 39 | Moo=0 40 | Moo::Role=0 41 | Scalar::Util=0 42 | Storable=3.08 43 | Time::HiRes=0 44 | 45 | [Acme::CPANModules::Whitelist] 46 | module=List::MoreUtils 47 | -------------------------------------------------------------------------------- /t/various.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | my $NUM_TEST_ITEMS = 4+3+3; 15 | 16 | test_parse( 17 | parse_file_args => ["t/data/various.org"], 18 | name => 'various', 19 | test_after_parse => sub { 20 | my (%args) = @_; 21 | my $doc = $args{result}; 22 | 23 | my $num_elems; 24 | my %num_elems; 25 | $doc->walk( 26 | sub { 27 | my $elem = shift; 28 | my $class = ref($elem); 29 | $num_elems{$class}++; 30 | $num_elems++; 31 | } 32 | ); 33 | 34 | is($num_elems, 41, 'num_elems'); 35 | is($num_elems{"Org::Element::Headline"}, 10, 'num_elems(Headline)'); 36 | 37 | }, 38 | ); 39 | 40 | done_testing(); 41 | -------------------------------------------------------------------------------- /t/comment.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'comment basic tests', 16 | filter_elements => 'Org::Element::Comment', 17 | doc => <<'_', 18 | # single line comment 19 | 20 | # *multi* 21 | #line 22 | # comment 23 | # 24 | 25 | # comment can be indented 26 | _ 27 | num => 3, 28 | test_after_parse => sub { 29 | my %args = @_; 30 | my $doc = $args{result}; 31 | my $elems = $args{elements}; 32 | #diag(explain [map {$_->as_string} @$elems]); 33 | is( $elems->[0]->as_string, "# single line comment\n", 34 | "comment[0] content"); 35 | is( $elems->[1]->as_string, "# *multi*\n#line\n# comment\n#\n", 36 | "comment[1] content"); 37 | ok(!$elems->[1]->children, 38 | "markup not parsed in comment"); 39 | }, 40 | ); 41 | 42 | done_testing(); 43 | -------------------------------------------------------------------------------- /lib/Org/Element/TimeRange.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::TimeRange; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | with 'Org::ElementRole'; 8 | with 'Org::ElementRole::Inline'; 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | has ts1 => (is => 'rw'); 16 | has ts2 => (is => 'rw'); 17 | 18 | sub as_string { 19 | my ($self) = @_; 20 | return $self->_str if $self->_str; 21 | join("", 22 | $self->ts1->as_string, 23 | "--", 24 | $self->ts2->as_string 25 | ); 26 | } 27 | 28 | sub as_text { 29 | goto \&as_string; 30 | } 31 | 32 | 1; 33 | # ABSTRACT: Represent Org time range (TS1--TS2) 34 | 35 | =head1 DESCRIPTION 36 | 37 | Derived from L. 38 | 39 | 40 | =head1 ATTRIBUTES 41 | 42 | =head2 ts1 => TIMESTAMP ELEMENT 43 | 44 | Starting timestamp. 45 | 46 | =head2 ts2 => TIMESTAMP ELEMENT 47 | 48 | Ending timestamp. 49 | 50 | 51 | =head1 METHODS 52 | 53 | =head2 as_string => str 54 | 55 | From L. 56 | 57 | =head2 as_text => str 58 | 59 | From L. 60 | -------------------------------------------------------------------------------- /lib/Org/ElementRole/Inline.pm: -------------------------------------------------------------------------------- 1 | package Org::ElementRole::Inline; 2 | 3 | use 5.010; 4 | use Moo::Role; 5 | 6 | requires 'as_text'; 7 | 8 | # AUTHORITY 9 | # DATE 10 | # DIST 11 | # VERSION 12 | 13 | sub is_block { 0 } 14 | 15 | sub is_inline { 1 } 16 | 17 | sub children_as_text { 18 | my ($self) = @_; 19 | return "" unless $self->children; 20 | join "", map {$_->as_text} @{$self->children}; 21 | } 22 | 23 | 1; 24 | # ABSTRACT: Role for inline elements 25 | 26 | =head1 DESCRIPTION 27 | 28 | This role is applied to elements that are "inline": elements that can occur 29 | inside text and put as a child of L. 30 | 31 | =head1 REQUIRES 32 | 33 | =head2 as_text => str 34 | 35 | Get the "rendered plaintext" representation of element. Most elements would 36 | return the same result as C, except for elements like 37 | L which will return link description instead of the link 38 | itself. 39 | 40 | 41 | =head1 METHODS 42 | 43 | =head2 is_block => bool (0) 44 | 45 | =head2 is_inline => bool (1) 46 | 47 | =head2 children_as_text => str 48 | -------------------------------------------------------------------------------- /t/fixed_width_section.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'non-fixed-width-section (missing space after colon)', 16 | filter_elements => 'Org::Element::FixedWidthSection', 17 | doc => <<'_', 18 | :foo 19 | _ 20 | num => 0, 21 | ); 22 | 23 | test_parse( 24 | name => 'basic tests', 25 | filter_elements => 'Org::Element::FixedWidthSection', 26 | doc => <<'_', 27 | : this is *an* example 28 | 29 | : this is another example 30 | 31 | : yet another 32 | : 33 | : with empty line 34 | _ 35 | num => 3, 36 | test_after_parse => sub { 37 | my %args = @_; 38 | my $elems = $args{elements}; 39 | is($elems->[0]->text, " this is *an* example\n", "#0: text()"); 40 | is($elems->[1]->text, "this is another example\n", "#1: text()"); 41 | is($elems->[2]->text, "yet another\n\nwith empty line\n", "#2: text()"); 42 | }, 43 | ); 44 | 45 | done_testing(); 46 | -------------------------------------------------------------------------------- /lib/Org/Element/RadioTarget.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::RadioTarget; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | with 'Org::ElementRole'; 8 | with 'Org::ElementRole::Inline'; 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | has target => (is => 'rw'); 16 | 17 | sub BUILD { 18 | my ($self, $args) = @_; 19 | my $pass = $args->{pass} // 1; 20 | my $doc = $self->document; 21 | if ($pass == 1) { 22 | push @{ $doc->radio_targets }, 23 | $self->target; 24 | } 25 | } 26 | 27 | sub as_string { 28 | my ($self) = @_; 29 | join("", 30 | "<<<", $self->target, ">>>"); 31 | } 32 | 33 | sub as_text { 34 | goto \&as_string; 35 | } 36 | 37 | 1; 38 | # ABSTRACT: Represent Org radio target 39 | 40 | =for Pod::Coverage ^(BUILD)$ 41 | 42 | =head1 DESCRIPTION 43 | 44 | Derived from L. 45 | 46 | 47 | =head1 ATTRIBUTES 48 | 49 | =head2 target 50 | 51 | 52 | =head1 METHODS 53 | 54 | =head2 as_string => str 55 | 56 | From L. 57 | 58 | =head2 as_text => str 59 | 60 | From L. 61 | 62 | =cut 63 | -------------------------------------------------------------------------------- /lib/Org/Element/ListItem.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::ListItem; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | 8 | # AUTHORITY 9 | # DATE 10 | # DIST 11 | # VERSION 12 | 13 | has bullet => (is => 'rw'); 14 | has check_state => (is => 'rw'); 15 | has desc_term => (is => 'rw'); 16 | 17 | sub header_as_string { 18 | my ($self) = @_; 19 | join("", 20 | $self->parent->indent, 21 | $self->bullet, " ", 22 | defined($self->check_state) ? "[".$self->check_state."]" : "", 23 | defined($self->desc_term) ? $self->desc_term->as_string . " ::" : "", 24 | ); 25 | } 26 | 27 | sub as_string { 28 | my ($self) = @_; 29 | $self->header_as_string . $self->children_as_string; 30 | } 31 | 32 | 1; 33 | #ABSTRACT: Represent Org list item 34 | 35 | =head1 DESCRIPTION 36 | 37 | Must have L as parent. 38 | 39 | Derived from L. 40 | 41 | 42 | =head1 ATTRIBUTES 43 | 44 | =head2 bullet 45 | 46 | =head2 check_state 47 | 48 | undef, " ", "X" or "-". 49 | 50 | =head2 desc_term 51 | 52 | Description term (for description list). 53 | 54 | 55 | =head1 METHODS 56 | 57 | =for Pod::Coverage header_as_string as_string 58 | 59 | =cut 60 | -------------------------------------------------------------------------------- /t/document.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Module::Loaded; 11 | use Org::Document; 12 | use Test::More 0.96; 13 | require "testlib.pl"; 14 | 15 | plan skip_all_if => 'Org::Element::Comment already loaded' 16 | if is_loaded('Org::Element::Comment'); 17 | 18 | my $doc = Org::Document->new; 19 | $doc->load_element_modules; 20 | ok(is_loaded('Org::Element::Comment'), 'Org::Element::Comment loaded'); 21 | 22 | subtest "cmp_priorities()" => sub { 23 | my $doc = Org::Document->new(from_string=>''); 24 | is($doc->cmp_priorities('A','A'), 0); 25 | is($doc->cmp_priorities('A','B'), -1); 26 | is($doc->cmp_priorities('B','A'), 1); 27 | ok(!defined($doc->cmp_priorities('B','X'))); 28 | ok(!defined($doc->cmp_priorities('X','A'))); 29 | ok(!defined($doc->cmp_priorities('X','X'))); 30 | 31 | $doc = Org::Document->new(from_string=>"#+PRIORITIES: A X B\n"); 32 | is($doc->cmp_priorities('A','A'), 0); 33 | is($doc->cmp_priorities('X','X'), 0); 34 | is($doc->cmp_priorities('A','X'), -1); 35 | is($doc->cmp_priorities('B','X'), 1); 36 | ok(!defined($doc->cmp_priorities('A','C'))); 37 | }; 38 | 39 | done_testing(); 40 | -------------------------------------------------------------------------------- /lib/Org/Element/List.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::List; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | 8 | # AUTHORITY 9 | # DATE 10 | # DIST 11 | # VERSION 12 | 13 | has indent => (is => 'rw'); 14 | has type => (is => 'rw'); 15 | has bullet_style => (is => 'rw'); 16 | 17 | sub items { 18 | my $self = shift; 19 | my @items; 20 | for (@{ $self->children }) { 21 | push @items, $_ if $_->isa('Org::Element::ListItem'); 22 | } 23 | \@items; 24 | } 25 | 26 | 1; 27 | # ABSTRACT: Represent Org list 28 | 29 | =for Pod::Coverage 30 | 31 | =head1 DESCRIPTION 32 | 33 | Must have L (or another ::List) as children. 34 | 35 | Derived from L. 36 | 37 | 38 | =head1 ATTRIBUTES 39 | 40 | =head2 indent 41 | 42 | Indent (e.g. " " x 2). 43 | 44 | =head2 type 45 | 46 | 'U' for unordered list (-, +, * for bullets), 'D' for description list, 'O' for 47 | ordered list (1., 2., 3., and so on). 48 | 49 | =head2 bullet_style 50 | 51 | E.g. '-', '*', '+'. For ordered list, currently just use '.' 52 | 53 | 54 | =head1 METHODS 55 | 56 | =head2 $list->items() => ARRAY OF OBJECTS 57 | 58 | Return the items, which are an arrayref of L objects. 59 | 60 | =cut 61 | -------------------------------------------------------------------------------- /t/radio_target.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'radio target basic tests', 16 | filter_elements => 'Org::Element::Link', 17 | doc => <<'_', 18 | target1, nottarget 1 19 | target 2, nottarget 2 20 | 21 | not target 22 | 2 23 | 24 | not 25 | target 26 | 27 | <<>> <<>> 28 | <<>> 30 | 31 | target1 32 | 33 | [[normal link]] 34 | _ 35 | num => 3 +1, 36 | test_after_parse => sub { 37 | my %args = @_; 38 | my $doc = $args{result}; 39 | my $elems = $args{elements}; 40 | is($elems->[0]->link, "target1" , "link[0]"); 41 | is($elems->[1]->link, "target 2", "link[1]"); 42 | is($elems->[2]->link, "target1" , "link[2]"); 43 | 44 | ok( $elems->[0]->from_radio_target, "from_radio_target[0]"); 45 | ok( $elems->[1]->from_radio_target, "from_radio_target[1]"); 46 | ok( $elems->[2]->from_radio_target, "from_radio_target[2]"); 47 | ok(!$elems->[3]->from_radio_target, "from_radio_target[3]"); 48 | }, 49 | ); 50 | 51 | done_testing(); 52 | 53 | -------------------------------------------------------------------------------- /lib/Org/Element/Link.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::Link; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | with 'Org::ElementRole'; 8 | with 'Org::ElementRole::Inline'; 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | has link => (is => 'rw'); 16 | has description => (is => 'rw'); 17 | has from_radio_target => (is => 'rw'); 18 | 19 | sub as_string { 20 | my ($self) = @_; 21 | return $self->_str if defined $self->_str; 22 | join("", 23 | "[", 24 | "[", $self->link, "]", 25 | (defined($self->description) && length($self->description) ? 26 | ("[", $self->description->as_string, "]") : ()), 27 | "]"); 28 | } 29 | 30 | sub as_text { 31 | my $self = shift; 32 | my $desc = $self->description; 33 | defined($desc) ? $desc->as_text : $self->link; 34 | } 35 | 36 | 1; 37 | # ABSTRACT: Represent Org hyperlink 38 | 39 | =head1 DESCRIPTION 40 | 41 | Derived from L. 42 | 43 | 44 | =head1 ATTRIBUTES 45 | 46 | =head2 link => STR 47 | 48 | =head2 description => OBJ 49 | 50 | =head2 from_radio_target => BOOL 51 | 52 | 53 | =head1 METHODS 54 | 55 | =head2 as_string => str 56 | 57 | From L. 58 | 59 | =head2 as_text => str 60 | 61 | From L. 62 | -------------------------------------------------------------------------------- /lib/Org/Element/TableRow.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::TableRow; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | 8 | # AUTHORITY 9 | # DATE 10 | # DIST 11 | # VERSION 12 | 13 | sub as_string { 14 | my ($self) = @_; 15 | return $self->_str if defined $self->_str; 16 | 17 | join("", 18 | "|", 19 | join("|", map {$_->as_string} @{$self->children}), 20 | "\n"); 21 | } 22 | 23 | sub as_array { 24 | my ($self) = @_; 25 | 26 | [map {$_->as_string} @{$self->children}]; 27 | } 28 | 29 | sub cells { 30 | my ($self) = @_; 31 | return [] unless $self->children; 32 | 33 | my $cells = []; 34 | for my $el (@{$self->children}) { 35 | push @$cells, $el if $el->isa('Org::Element::TableCell'); 36 | } 37 | $cells; 38 | } 39 | 40 | 1; 41 | # ABSTRACT: Represent Org table row 42 | 43 | =for Pod::Coverage as_string 44 | 45 | =head1 DESCRIPTION 46 | 47 | Derived from L. Must have L 48 | instances as its children. 49 | 50 | 51 | =head1 ATTRIBUTES 52 | 53 | 54 | =head1 METHODS 55 | 56 | =head2 $table->cells() => ELEMENTS 57 | 58 | Return the cells of the row. 59 | 60 | =head2 $table->as_array() => ARRAY 61 | 62 | Return an arrayref containing the cells of the row, each cells already 63 | stringified with as_string(). 64 | 65 | =cut 66 | -------------------------------------------------------------------------------- /lib/Org/Element/FixedWidthSection.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::FixedWidthSection; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | with 'Org::ElementRole'; 8 | with 'Org::ElementRole::Block'; 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | sub text { 16 | my ($self) = @_; 17 | my $res = $self->_str; 18 | $res =~ s/^[ \t]*: ?//mg; 19 | $res; 20 | } 21 | 22 | 1; 23 | # ABSTRACT: Represent Org fixed-width section 24 | 25 | =head1 SYNOPSIS 26 | 27 | use Org::Element::FixedWidthSection; 28 | my $el = Org::Element::FixedWidthSection->new(_str => ": line1\n: line2\n"); 29 | 30 | 31 | =head1 DESCRIPTION 32 | 33 | Fixed width section is a block of text where each line is prefixed by colon + 34 | space (or just a colon + space or a colon). Example: 35 | 36 | Here is an example: 37 | : some example from a text file. 38 | : second line. 39 | : 40 | : fourth line, after the empty above. 41 | 42 | which is functionally equivalent to: 43 | 44 | Here is an example: 45 | #+BEGIN_EXAMPLE 46 | some example from a text file. 47 | another example. 48 | 49 | fourth line, after the empty above. 50 | #+END_EXAMPLE 51 | 52 | Derived from L. 53 | 54 | 55 | =head1 ATTRIBUTES 56 | 57 | 58 | =head1 METHODS 59 | 60 | =head2 $el->text => STR 61 | 62 | The text (without colon prefix). 63 | 64 | 65 | =for Pod::Coverage as_string BUILD 66 | 67 | =cut 68 | -------------------------------------------------------------------------------- /lib/Org/Element/Footnote.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::Footnote; 2 | 3 | use 5.010; 4 | use locale; 5 | use Log::ger; 6 | use Moo; 7 | extends 'Org::Element'; 8 | with 'Org::ElementRole'; 9 | with 'Org::ElementRole::Inline'; 10 | 11 | # AUTHORITY 12 | # DATE 13 | # DIST 14 | # VERSION 15 | 16 | has name => (is => 'rw'); 17 | has is_ref => (is => 'rw'); 18 | has def => (is => 'rw'); 19 | 20 | sub BUILD { 21 | my ($self, $args) = @_; 22 | log_trace("name = %s", $self->name); 23 | } 24 | 25 | sub as_string { 26 | my ($self) = @_; 27 | 28 | join("", 29 | "[fn:", ($self->name // ""), 30 | defined($self->def) ? ":".$self->def->as_string : "", 31 | "]"); 32 | } 33 | 34 | sub as_text { 35 | goto \&as_string; 36 | } 37 | 38 | 1; 39 | # ABSTRACT: Represent Org footnote reference and/or definition 40 | 41 | =for Pod::Coverage ^(BUILD)$ 42 | 43 | =head1 DESCRIPTION 44 | 45 | Derived from L. 46 | 47 | 48 | =head1 ATTRIBUTES 49 | 50 | =head2 name => STR|undef 51 | 52 | Can be undef, for anonymous footnote (but in case of undef, is_ref must be 53 | true and def must also be set). 54 | 55 | =head2 is_ref => BOOL 56 | 57 | Set to true to make this a footnote reference. 58 | 59 | =head2 def => TEXT ELEMENT 60 | 61 | Set to make this a footnote definition. 62 | 63 | 64 | =head1 METHODS 65 | 66 | =head2 as_string => str 67 | 68 | From L. 69 | 70 | =head2 as_text => str 71 | 72 | From L. 73 | -------------------------------------------------------------------------------- /t/base_element-field_name.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'field_name() (text)', 16 | doc => <<'_', 17 | DEADLINE: <2011-06-09 > 18 | DEADLINE <2011-06-09 > 19 | foo 20 | bar baz : <2011-06-09 > 21 | 22 | - item 23 | - item 2: <2011-06-09 > 24 | _ 25 | test_after_parse => sub { 26 | my (%args) = @_; 27 | my $doc = $args{result}; 28 | 29 | my ($ts1, $ts2, $ts3, $ts4) = $doc->find('Timestamp'); 30 | is( $ts1->field_name, "DEADLINE"); 31 | ok(!$ts2->field_name); 32 | is( $ts3->field_name, "bar baz"); 33 | is( $ts4->field_name, "item 2"); 34 | }, 35 | ); 36 | 37 | test_parse( 38 | name => 'field_name() (desc_term)', 39 | doc => <<'_', 40 | - name1 :: value 41 | - name2 :: <2011-06-09 > 42 | _ 43 | test_after_parse => sub { 44 | my (%args) = @_; 45 | my $doc = $args{result}; 46 | 47 | my ($ts1) = $doc->find('Timestamp'); 48 | is( $ts1->field_name, "name2"); 49 | }, 50 | ); 51 | 52 | # TODO 53 | test_parse( 54 | name => 'field_name() (properties)', 55 | doc => <<'_', 56 | * first last 57 | :PROPERTIES: 58 | :birthday: (5 7 1970) 59 | :email: foo@bar.com 60 | :END: 61 | _ 62 | test_after_parse => sub { 63 | my (%args) = @_; 64 | my $doc = $args{result}; 65 | }, 66 | ); 67 | 68 | done_testing(); 69 | -------------------------------------------------------------------------------- /t/footnote.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'footnote basic tests', 16 | filter_elements => 'Org::Element::Footnote', 17 | doc => <<'_', 18 | # footnotes 19 | 20 | [1] 21 | [fn:a] 22 | [fn:b:inline definition] 23 | [fn:c] definition 24 | [fn::anon inline definition] 25 | 26 | # non-footnotes 27 | 28 | [fn:name 29 | with newline] 30 | 31 | [fn:name:definition 32 | with newline] 33 | _ 34 | num => 5, 35 | test_after_parse => sub { 36 | my %args = @_; 37 | my $doc = $args{result}; 38 | my $fn = $args{elements}; 39 | 40 | is( $fn->[0]->name, 1, "fn0 name"); 41 | ok( $fn->[0]->is_ref, "fn0 is ref"); 42 | ok(!$fn->[0]->def, "fn0 no def"); 43 | 44 | is( $fn->[1]->name, "a", "fn1 name"); 45 | ok( $fn->[1]->is_ref, "fn1 is ref"); 46 | ok(!$fn->[1]->def, "fn1 no def"); 47 | 48 | is( $fn->[2]->name, "b", "fn2 name"); 49 | ok(!$fn->[2]->is_ref, "fn2 not ref"); 50 | is( $fn->[2]->def->as_string, "inline definition", "fn2 def"); 51 | 52 | is( $fn->[3]->name, "c", "fn3 name"); 53 | ok(!$fn->[3]->is_ref, "fn3 not ref"); 54 | is( $fn->[3]->def->as_string, "definition", "fn3 def"); 55 | 56 | ok(!$fn->[4]->name, "fn4 anon"); 57 | ok( $fn->[4]->is_ref, "fn4 is ref"); 58 | is( $fn->[4]->def->as_string, "anon inline definition", "fn4 def"); 59 | }, 60 | ); 61 | 62 | done_testing(); 63 | 64 | -------------------------------------------------------------------------------- /t/setting-todo.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | my $NUM_TEST_ITEMS = 4+3+3; 15 | 16 | test_parse( 17 | parse_file_args => ["t/data/custom_todo_kw.org"], 18 | name => 'setting: TODO', 19 | filter_elements => 'Org::Element::Headline', 20 | num => 3 + $NUM_TEST_ITEMS, 21 | test_after_parse => sub { 22 | my (%args) = @_; 23 | my $elems = $args{elements}; 24 | my $num_test_items = 0; 25 | 26 | for my $el (@$elems) { 27 | my $title = $el->title->as_string; 28 | my $re = qr/(?: (?:([A-Z]+)=([^;]*)) (?:;\s|\z) )/x; 29 | my $h = $el->as_string; $h =~ s/\R.*//s; 30 | #diag "heading='$h', ". 31 | # "is_todo=".($el->is_todo//0).", is_done=".($el->is_done//0); 32 | next unless $title =~ /$re/; 33 | $num_test_items++; 34 | my %v; 35 | while ($title =~ s/$re//) { $v{$1} = $2 } 36 | #diag explain \%v; 37 | if ($v{RES} =~ /todo/) { 38 | ok( $el->is_todo, "#$num_test_items is a todo ($v{NOTE})"); 39 | } else { 40 | ok(!$el->is_todo, "#$num_test_items not a todo ($v{NOTE})"); 41 | } 42 | if ($v{RES} =~ /done/) { 43 | ok( $el->is_done, "#$num_test_items is a done ($v{NOTE})"); 44 | } else { 45 | ok(!$el->is_done, "#$num_test_items not a done ($v{NOTE})"); 46 | } 47 | } 48 | 49 | is($num_test_items, $NUM_TEST_ITEMS, "num_test_items"); 50 | }, 51 | ); 52 | 53 | done_testing(); 54 | -------------------------------------------------------------------------------- /t/timerange.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use DateTime; 11 | use Org::Parser; 12 | use Test::More 0.96; 13 | require "testlib.pl"; 14 | 15 | test_parse( 16 | name => 'timerange basic tests', 17 | filter_elements => sub { 18 | $_[0]->isa('Org::Element::TimeRange') }, 19 | doc => <<'_', 20 | * TODO active timeranges 21 | <2011-03-23 Wed>--<2011-03-24 Thu> 22 | <2011-03-23 >--<2011-03-24 > 23 | <2011-03-23 Wed 01:23>--<2011-03-23 03:59> 24 | 25 | * inactive timeranges 26 | [2011-03-23 Wed]--[2011-03-24 Thu] 27 | [2011-03-23 ]--[2011-03-24 ] 28 | [2011-03-23 Wed 01:23]--[2011-03-23 Wed 03:59] 29 | 30 | * non-timeranges 31 | [2011-03-22 ]--<2011-03-23 > # mixed active & inactive timestamp 32 | <2011-03-22 >--[2011-03-23 ] # mixed active & inactive timestamp 33 | 34 | _ 35 | num => 6, 36 | test_after_parse => sub { 37 | my %args = @_; 38 | my $doc = $args{result}; 39 | my $elems = $args{elements}; 40 | ok( $elems->[0]->ts1->is_active, "tr[0] is_active"); 41 | ok(!$elems->[3]->ts1->is_active, "tr[3] !is_active"); 42 | }, 43 | ); 44 | 45 | test_parse( 46 | name => 'event duration not allowed in timerange', 47 | filter_elements => sub { 48 | $_[0]->isa('Org::Element::TimeRange') }, 49 | doc => <<'_', 50 | <2011-03-23 Wed 11:28-12:00>--<2011-03-24 Thu> 51 | _ 52 | dies => 1, 53 | ); 54 | 55 | test_parse( 56 | name => 'repeater not allowed in timerange', 57 | filter_elements => sub { 58 | $_[0]->isa('Org::Element::TimeRange') }, 59 | doc => <<'_', 60 | <2011-03-23 Wed +1w>--<2011-03-24 Thu> 61 | _ 62 | dies => 1, 63 | ); 64 | 65 | done_testing(); 66 | -------------------------------------------------------------------------------- /t/block.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'unknown block', 16 | filter_elements => 'Org::Element::Block', 17 | doc => <<'_', 18 | #+BEGIN_FOO 19 | bar 20 | #+END_FOO 21 | _ 22 | dies => 1, 23 | ); 24 | 25 | test_parse( 26 | name => 'EXAMPLE: undetected (no END, becomes comment)', 27 | filter_elements => 'Org::Element::Block', 28 | doc => <<'_', 29 | #+BEGIN_EXAMPLE 30 | 1 31 | 2 32 | #+xEND_EXAMPLE 33 | _ 34 | dies => 0, 35 | num => 0, 36 | ); 37 | 38 | # also checks case-sensitiveness 39 | test_parse( 40 | name => 'EXAMPLE: basic tests', 41 | filter_elements => 'Org::Element::Block', 42 | doc => <<'_', 43 | #+BEGIN_EXAMPLE -t -w 40 44 | #+INSIDE 45 | line 2 46 | #+end_EXAMPLE 47 | _ 48 | num => 1, 49 | test_after_parse => sub { 50 | my %args = @_; 51 | my $doc = $args{result}; 52 | my $elems = $args{elements}; 53 | my $bl = $elems->[0]; 54 | is($bl->name, "EXAMPLE", "name"); 55 | is_deeply($bl->args, ["-t", "-w", 40], "args"); 56 | is($bl->raw_content, "#+INSIDE\nline 2", "raw_content"); 57 | }, 58 | ); 59 | 60 | test_parse( 61 | name => 'block is indentable', 62 | filter_elements => 'Org::Element::Block', 63 | doc => <<'_', 64 | #+BEGIN_EXAMPLE 65 | foo 66 | #+END_EXAMPLE 67 | _ 68 | num => 1, 69 | test_after_parse => sub { 70 | my %args = @_; 71 | my $elems = $args{elements}; 72 | is($elems->[0]->begin_indent, " ", "begin_indent attribute"); 73 | is($elems->[0]->end_indent, " ", "end_indent attribute"); 74 | }, 75 | ); 76 | 77 | done_testing(); 78 | -------------------------------------------------------------------------------- /t/headline-get_property.t: -------------------------------------------------------------------------------- 1 | #!perl 2 | 3 | use 5.010; 4 | use strict; 5 | use warnings; 6 | 7 | use FindBin '$Bin'; 8 | use lib $Bin, "$Bin/t"; 9 | 10 | use Org::Parser; 11 | use Test::More 0.96; 12 | require "testlib.pl"; 13 | 14 | test_parse( 15 | name => 'get_property()', 16 | filter_elements => 'Org::Element::Headline', 17 | doc => <<'_', 18 | #+PROPERTY: x 1 19 | #+PROPERTY: y 1 20 | * head1 21 | some text 22 | :PROPERTIES: 23 | :x: 2 24 | :p: 4 25 | :END: 26 | ** head2 27 | some text 28 | :PROPERTIES: 29 | :z: 3 30 | :END: 31 | :LOGBOOK: 32 | :z: 5 33 | :END: 34 | *** head3 35 | some text 36 | :PROPERTIES: 37 | :z: 6 38 | :END: 39 | _ 40 | num => 3, 41 | test_after_parse => sub { 42 | my (%args) = @_; 43 | my $doc = $args{result}; 44 | my $elems = $args{elements}; 45 | my $h1 = $elems->[0]; 46 | my $h2 = $elems->[1]; 47 | my $h3 = $elems->[2]; 48 | 49 | is($h1->get_property('x'), 2, "h1->get_property(x)"); # from own 50 | is($h1->get_property('y'), 1, "h1->get_property(y)"); # from file-wide property 51 | ok(!$h1->get_property('z'), "h1->get_property(z)"); # not found 52 | 53 | is($h2->get_property('z'), 3, "h2->get_property(z)"); # from own 54 | is($h2->get_drawer("LOGBOOK")->properties->{'z'}, 5, "h2->get_drawer(LOGBOOK) z=5"); # from a named drawer other than PROPERTIES 55 | ok(!$h2->get_property('p'), "h2->get_property(p)"); # not found 56 | is($h2->get_property('p', 1), 4, "h2->get_property(p,1)"); # from parent 57 | is($h2->get_property('y'), 1, "h2->get_property(y)"); # from file-wide property 58 | 59 | is($h3->get_property('z'), 6, "h3->get_property(z)"); # from own 60 | is($h3->get_property('x', 1), 2, "h3->get_property(x,1)"); # from grand-parent 61 | 62 | }, 63 | ); 64 | 65 | done_testing(); 66 | -------------------------------------------------------------------------------- /lib/Org/Element/Block.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::Block; 2 | 3 | use 5.010; 4 | use locale; 5 | 6 | use Moo; 7 | extends 'Org::Element'; 8 | with 'Org::ElementRole'; 9 | with 'Org::ElementRole::Block'; 10 | 11 | # AUTHORITY 12 | # DATE 13 | # DIST 14 | # VERSION 15 | 16 | has name => (is => 'rw'); 17 | has args => (is => 'rw'); 18 | has raw_content => (is => 'rw'); 19 | has begin_indent => (is => 'rw'); 20 | has end_indent => (is => 'rw'); 21 | 22 | my @known_blocks = qw( 23 | ASCII CENTER COMMENT EXAMPLE HTML 24 | LATEX QUOTE SRC VERSE 25 | ); 26 | 27 | sub BUILD { 28 | my ($self, $args) = @_; 29 | $self->name(uc $self->name); 30 | (grep { $_ eq $self->name } @known_blocks) 31 | or $self->die("Unknown block name: ".$self->name); 32 | } 33 | 34 | sub element_as_string { 35 | my ($self) = @_; 36 | return $self->_str if defined $self->_str; 37 | join("", 38 | $self->begin_indent // "", 39 | "#+BEGIN_".uc($self->name), 40 | $self->args && @{$self->args} ? 41 | " ".Org::Document::__format_args($self->args) : "", 42 | "\n", 43 | $self->raw_content, 44 | $self->end_indent // "", 45 | "#+END_".uc($self->name)."\n"); 46 | } 47 | 48 | 1; 49 | # ABSTRACT: Represent Org block 50 | 51 | =for Pod::Coverage element_as_string BUILD 52 | 53 | =head1 DESCRIPTION 54 | 55 | Derived from L. 56 | 57 | 58 | =head1 ATTRIBUTES 59 | 60 | =head2 name => STR 61 | 62 | Block name. For example, #+begin_src ... #+end_src is an 'SRC' block. 63 | 64 | =head2 args => ARRAY 65 | 66 | =head2 raw_content => STR 67 | 68 | =head2 begin_indent => STR 69 | 70 | Indentation on begin line (before C<#+BEGIN>), or empty string if none. 71 | 72 | =head2 end_indent => STR 73 | 74 | Indentation on end line (before C<#+END>), or empty string if none. 75 | 76 | 77 | =head1 METHODS 78 | 79 | =cut 80 | -------------------------------------------------------------------------------- /lib/Org/Element/Drawer.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::Drawer; 2 | 3 | use 5.010; 4 | use locale; 5 | 6 | use Moo; 7 | extends 'Org::Element'; 8 | with 'Org::ElementRole'; 9 | with 'Org::ElementRole::Block'; 10 | 11 | # AUTHORITY 12 | # DATE 13 | # DIST 14 | # VERSION 15 | 16 | has name => (is => 'rw'); 17 | has properties => (is => 'rw'); 18 | 19 | sub BUILD { 20 | my ($self, $args) = @_; 21 | my $doc = $self->document; 22 | my $pass = $args->{pass} // 1; 23 | 24 | if ($pass == 2) { 25 | $self->die("Unknown drawer name: ".$self->name) 26 | unless grep { $_ eq $self->name } @{$doc->drawer_names}; 27 | } 28 | } 29 | 30 | sub _parse_properties { 31 | my ($self, $raw_content) = @_; 32 | $self->properties({}) unless $self->properties; 33 | while ($raw_content =~ /^[ \t]*:(\w+):[ \t]+ 34 | ($Org::Document::args_re)[ \t]*(?:\R|\z)/mxg) { 35 | $self->properties->{$1} = $2; 36 | } 37 | } 38 | 39 | sub as_string { 40 | my ($self) = @_; 41 | join("", 42 | ":", $self->name, ":\n", 43 | $self->children_as_string, 44 | ":END:"); 45 | } 46 | 47 | 1; 48 | # ABSTRACT: Represent Org drawer 49 | 50 | =for Pod::Coverage BUILD as_string 51 | 52 | =head1 DESCRIPTION 53 | 54 | Derived from L. 55 | 56 | Example of a drawer in an Org document: 57 | 58 | * A heading 59 | :SOMEDRAWER: 60 | some text 61 | more text ... 62 | :END: 63 | 64 | A special drawer named C is used to store a list of properties: 65 | 66 | * A heading 67 | :PROPERTIES: 68 | :Title: the title 69 | :Publisher: the publisher 70 | :END: 71 | 72 | 73 | =head1 ATTRIBUTES 74 | 75 | =head2 name => STR 76 | 77 | Drawer name. 78 | 79 | =head2 properties => HASH 80 | 81 | Collected properties in the drawer. In the example properties drawer above, 82 | C will result in: 83 | 84 | { 85 | Title => "the title", 86 | Publisher => "the publisher", 87 | } 88 | 89 | 90 | =head1 METHODS 91 | 92 | =cut 93 | -------------------------------------------------------------------------------- /lib/Org/Element/Text.pm: -------------------------------------------------------------------------------- 1 | package Org::Element::Text; 2 | 3 | use 5.010; 4 | use locale; 5 | use Moo; 6 | extends 'Org::Element'; 7 | with 'Org::ElementRole'; 8 | with 'Org::ElementRole::Inline'; 9 | 10 | # AUTHORITY 11 | # DATE 12 | # DIST 13 | # VERSION 14 | 15 | has text => (is => 'rw'); 16 | has style => (is => 'rw'); 17 | 18 | our %mu2style = (''=>'', '*'=>'B', '_'=>'U', '/'=>'I', 19 | '+'=>'S', '='=>'C', '~'=>'V'); 20 | our %style2mu = reverse(%mu2style); 21 | 22 | sub as_string { 23 | my ($self) = @_; 24 | my $muchar = $style2mu{$self->style // ''} // ''; 25 | 26 | join("", 27 | $muchar, 28 | $self->text // '', $self->children_as_string, 29 | $muchar); 30 | } 31 | 32 | sub as_text { 33 | my $self = shift; 34 | my $muchar = $style2mu{$self->style // ''} // ''; 35 | 36 | join("", 37 | $muchar, 38 | $self->text // '', $self->children_as_text, 39 | $muchar); 40 | } 41 | 42 | 1; 43 | # ABSTRACT: Represent text 44 | 45 | =for Pod::Coverage as_string 46 | 47 | =head1 DESCRIPTION 48 | 49 | Derived from L. 50 | 51 | Org::Element::Text is an object that represents a piece of text. It has C 52 | and C