2023-04-21 22:36:01 +00:00
use crate ::error ::Res ;
2023-04-21 20:57:49 +00:00
use nom ::branch ::alt ;
use nom ::bytes ::complete ::is_not ;
use nom ::bytes ::complete ::tag_no_case ;
use nom ::character ::complete ::line_ending ;
use nom ::character ::complete ::space0 ;
use nom ::character ::complete ::space1 ;
use nom ::combinator ::consumed ;
use nom ::combinator ::eof ;
2023-04-21 21:32:53 +00:00
use nom ::combinator ::map ;
2023-04-21 20:57:49 +00:00
use nom ::combinator ::opt ;
use nom ::combinator ::verify ;
use nom ::multi ::many_till ;
use nom ::sequence ::tuple ;
2023-04-21 20:31:25 +00:00
use super ::Context ;
2023-04-21 20:57:49 +00:00
use crate ::parser ::exiting ::ExitClass ;
2023-04-21 21:40:49 +00:00
use crate ::parser ::lesser_element ::CommentBlock ;
use crate ::parser ::lesser_element ::ExampleBlock ;
use crate ::parser ::lesser_element ::ExportBlock ;
use crate ::parser ::lesser_element ::SrcBlock ;
use crate ::parser ::lesser_element ::VerseBlock ;
2023-04-21 22:01:06 +00:00
use crate ::parser ::object ::Object ;
use crate ::parser ::object ::PlainText ;
2023-04-21 20:57:49 +00:00
use crate ::parser ::object_parser ::standard_set_object ;
use crate ::parser ::parser_context ::ContextElement ;
use crate ::parser ::parser_context ::ExitMatcherNode ;
use crate ::parser ::parser_with_context ::parser_with_context ;
2023-04-21 21:32:53 +00:00
use crate ::parser ::plain_text ::plain_text ;
2023-04-21 20:57:49 +00:00
use crate ::parser ::util ::blank_line ;
use crate ::parser ::util ::exit_matcher_parser ;
use crate ::parser ::util ::get_consumed ;
use crate ::parser ::util ::maybe_consume_trailing_whitespace_if_not_exiting ;
use crate ::parser ::util ::start_of_line ;
2023-04-21 20:31:25 +00:00
#[ tracing::instrument(ret, level = " debug " ) ]
2023-04-21 21:40:49 +00:00
pub fn verse_block < ' r , ' s > (
2023-04-21 20:31:25 +00:00
context : Context < ' r , ' s > ,
input : & ' s str ,
2023-04-21 21:40:49 +00:00
) -> Res < & ' s str , VerseBlock < ' s > > {
2023-04-21 22:01:06 +00:00
let ( remaining , name ) = lesser_block_begin ( " verse " ) ( context , input ) ? ;
2023-04-21 20:57:49 +00:00
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
let ( remaining , _nl ) = line_ending ( remaining ) ? ;
2023-04-21 22:01:06 +00:00
let lesser_block_end_specialized = lesser_block_end ( " verse " ) ;
2023-04-21 20:57:49 +00:00
let parser_context = context
. with_additional_node ( ContextElement ::ConsumeTrailingWhitespace ( true ) )
. with_additional_node ( ContextElement ::Context ( " lesser block " ) )
. with_additional_node ( ContextElement ::ExitMatcherNode ( ExitMatcherNode {
class : ExitClass ::Beta ,
2023-04-21 21:11:27 +00:00
exit_matcher : & lesser_block_end_specialized ,
2023-04-21 20:57:49 +00:00
} ) ) ;
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
let object_matcher = parser_with_context! ( standard_set_object ) ( & parser_context ) ;
let exit_matcher = parser_with_context! ( exit_matcher_parser ) ( & parser_context ) ;
// Check for a completely empty block
let ( remaining , children ) = match consumed ( many_till ( blank_line , exit_matcher ) ) ( remaining ) {
2023-04-21 22:01:06 +00:00
Ok ( ( remaining , ( whitespace , ( _children , _exit_contents ) ) ) ) = > (
remaining ,
vec! [ Object ::PlainText ( PlainText { source : whitespace } ) ] ,
) ,
2023-04-21 20:57:49 +00:00
Err ( _ ) = > {
let ( remaining , ( children , _exit_contents ) ) =
many_till ( object_matcher , exit_matcher ) ( remaining ) ? ;
( remaining , children )
}
} ;
2023-04-21 21:11:27 +00:00
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
2023-04-21 20:57:49 +00:00
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
2023-04-21 21:40:49 +00:00
VerseBlock {
2023-04-21 20:57:49 +00:00
source ,
name ,
data : parameters ,
children ,
} ,
) )
}
2023-04-21 21:32:53 +00:00
#[ tracing::instrument(ret, level = " debug " ) ]
2023-04-21 21:40:49 +00:00
pub fn comment_block < ' r , ' s > (
2023-04-21 21:32:53 +00:00
context : Context < ' r , ' s > ,
input : & ' s str ,
2023-04-21 21:40:49 +00:00
) -> Res < & ' s str , CommentBlock < ' s > > {
2023-04-21 21:32:53 +00:00
let ( remaining , name ) = lesser_block_begin ( " comment " ) ( context , input ) ? ;
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
let ( remaining , _nl ) = line_ending ( remaining ) ? ;
let lesser_block_end_specialized = lesser_block_end ( " comment " ) ;
let parser_context = context
. with_additional_node ( ContextElement ::ConsumeTrailingWhitespace ( true ) )
. with_additional_node ( ContextElement ::Context ( " lesser block " ) )
. with_additional_node ( ContextElement ::ExitMatcherNode ( ExitMatcherNode {
class : ExitClass ::Beta ,
exit_matcher : & lesser_block_end_specialized ,
} ) ) ;
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
let ( remaining , contents ) = map ( parser_with_context! ( plain_text ) ( & parser_context ) , | obj | {
obj . source
} ) ( remaining ) ? ;
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
2023-04-21 21:40:49 +00:00
CommentBlock {
source ,
name ,
data : parameters ,
contents ,
} ,
) )
}
#[ tracing::instrument(ret, level = " debug " ) ]
pub fn example_block < ' r , ' s > (
context : Context < ' r , ' s > ,
input : & ' s str ,
) -> Res < & ' s str , ExampleBlock < ' s > > {
let ( remaining , name ) = lesser_block_begin ( " example " ) ( context , input ) ? ;
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
let ( remaining , _nl ) = line_ending ( remaining ) ? ;
let lesser_block_end_specialized = lesser_block_end ( " example " ) ;
let parser_context = context
. with_additional_node ( ContextElement ::ConsumeTrailingWhitespace ( true ) )
. with_additional_node ( ContextElement ::Context ( " lesser block " ) )
. with_additional_node ( ContextElement ::ExitMatcherNode ( ExitMatcherNode {
class : ExitClass ::Beta ,
exit_matcher : & lesser_block_end_specialized ,
} ) ) ;
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
let ( remaining , contents ) = map ( parser_with_context! ( plain_text ) ( & parser_context ) , | obj | {
obj . source
} ) ( remaining ) ? ;
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
ExampleBlock {
source ,
name ,
data : parameters ,
contents ,
} ,
) )
}
#[ tracing::instrument(ret, level = " debug " ) ]
pub fn export_block < ' r , ' s > (
context : Context < ' r , ' s > ,
input : & ' s str ,
) -> Res < & ' s str , ExportBlock < ' s > > {
let ( remaining , name ) = lesser_block_begin ( " export " ) ( context , input ) ? ;
2023-04-21 22:11:26 +00:00
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
2023-04-21 21:40:49 +00:00
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
let ( remaining , _nl ) = line_ending ( remaining ) ? ;
let lesser_block_end_specialized = lesser_block_end ( " export " ) ;
let parser_context = context
. with_additional_node ( ContextElement ::ConsumeTrailingWhitespace ( true ) )
. with_additional_node ( ContextElement ::Context ( " lesser block " ) )
. with_additional_node ( ContextElement ::ExitMatcherNode ( ExitMatcherNode {
class : ExitClass ::Beta ,
exit_matcher : & lesser_block_end_specialized ,
} ) ) ;
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
let ( remaining , contents ) = map ( parser_with_context! ( plain_text ) ( & parser_context ) , | obj | {
obj . source
} ) ( remaining ) ? ;
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
ExportBlock {
source ,
name ,
data : parameters ,
contents ,
} ,
) )
}
#[ tracing::instrument(ret, level = " debug " ) ]
pub fn src_block < ' r , ' s > ( context : Context < ' r , ' s > , input : & ' s str ) -> Res < & ' s str , SrcBlock < ' s > > {
let ( remaining , name ) = lesser_block_begin ( " src " ) ( context , input ) ? ;
2023-04-21 22:13:23 +00:00
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
2023-04-21 21:40:49 +00:00
let ( remaining , parameters ) = opt ( tuple ( ( space1 , data ) ) ) ( remaining ) ? ;
let ( remaining , _nl ) = line_ending ( remaining ) ? ;
let lesser_block_end_specialized = lesser_block_end ( " src " ) ;
let parser_context = context
. with_additional_node ( ContextElement ::ConsumeTrailingWhitespace ( true ) )
. with_additional_node ( ContextElement ::Context ( " lesser block " ) )
. with_additional_node ( ContextElement ::ExitMatcherNode ( ExitMatcherNode {
class : ExitClass ::Beta ,
exit_matcher : & lesser_block_end_specialized ,
} ) ) ;
let parameters = match parameters {
Some ( ( _ws , parameters ) ) = > Some ( parameters ) ,
None = > None ,
} ;
let ( remaining , contents ) = map ( parser_with_context! ( plain_text ) ( & parser_context ) , | obj | {
obj . source
} ) ( remaining ) ? ;
let ( remaining , _end ) = lesser_block_end_specialized ( & parser_context , remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
SrcBlock {
2023-04-21 21:32:53 +00:00
source ,
name ,
data : parameters ,
contents ,
} ,
) )
}
2023-04-21 20:57:49 +00:00
#[ tracing::instrument(ret, level = " debug " ) ]
fn name < ' s > ( input : & ' s str ) -> Res < & ' s str , & ' s str > {
is_not ( " \t \r \n " ) ( input )
}
#[ tracing::instrument(ret, level = " debug " ) ]
fn data < ' s > ( input : & ' s str ) -> Res < & ' s str , & ' s str > {
is_not ( " \r \n " ) ( input )
}
2023-04-21 21:16:57 +00:00
fn lesser_block_end (
current_name : & str ,
) -> impl for < ' r , ' s > Fn ( Context < ' r , ' s > , & ' s str ) -> Res < & ' s str , & ' s str > {
let current_name_lower = current_name . to_lowercase ( ) ;
2023-04-21 21:11:27 +00:00
move | context : Context , input : & str | {
start_of_line ( context , input ) ? ;
let ( remaining , _leading_whitespace ) = space0 ( input ) ? ;
let ( remaining , ( _begin , _name , _ws ) ) = tuple ( (
tag_no_case ( " #+end_ " ) ,
2023-04-21 21:16:57 +00:00
tag_no_case ( current_name_lower . as_str ( ) ) ,
2023-04-21 21:11:27 +00:00
alt ( ( eof , line_ending ) ) ,
) ) ( remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( ( remaining , source ) )
}
2023-04-21 20:31:25 +00:00
}
2023-04-21 21:32:53 +00:00
fn lesser_block_begin (
current_name : & str ,
) -> impl for < ' r , ' s > Fn ( Context < ' r , ' s > , & ' s str ) -> Res < & ' s str , & ' s str > {
let current_name_lower = current_name . to_lowercase ( ) ;
move | context : Context , input : & str | {
start_of_line ( context , input ) ? ;
let ( remaining , _leading_whitespace ) = space0 ( input ) ? ;
let ( remaining , ( _begin , name ) ) = tuple ( (
tag_no_case ( " #+begin_ " ) ,
verify ( name , | name : & str | {
name . to_lowercase ( ) . as_str ( ) = = current_name_lower
} ) ,
) ) ( remaining ) ? ;
Ok ( ( remaining , name ) )
}
}