2023-07-21 17:52:18 -04:00
use nom ::branch ::alt ;
use nom ::bytes ::complete ::tag ;
use nom ::bytes ::complete ::tag_no_case ;
use nom ::character ::complete ::anychar ;
use nom ::combinator ::opt ;
use nom ::combinator ::recognize ;
use nom ::combinator ::verify ;
use nom ::multi ::many1 ;
use nom ::multi ::many_till ;
2023-07-21 18:42:22 -04:00
use nom ::multi ::separated_list1 ;
2023-07-21 17:52:18 -04:00
use nom ::sequence ::tuple ;
2023-08-28 00:34:48 -04:00
use super ::citation_reference ::must_balance_bracket ;
2023-08-29 11:18:15 -04:00
use super ::org_source ::BracketDepth ;
2023-08-23 00:30:26 -04:00
use super ::org_source ::OrgSource ;
2023-08-31 15:44:44 -04:00
use super ::util ::maybe_consume_object_trailing_whitespace_if_not_exiting ;
2023-07-21 17:52:18 -04:00
use crate ::error ::CustomError ;
2023-07-20 00:38:16 -04:00
use crate ::error ::Res ;
2023-07-21 17:52:18 -04:00
use crate ::parser ::citation_reference ::citation_reference ;
use crate ::parser ::citation_reference ::citation_reference_key ;
2023-07-20 00:38:16 -04:00
use crate ::parser ::object ::Citation ;
2023-07-21 17:52:18 -04:00
use crate ::parser ::object_parser ::standard_set_object ;
use crate ::parser ::util ::exit_matcher_parser ;
use crate ::parser ::util ::get_consumed ;
use crate ::parser ::Object ;
2023-07-20 00:38:16 -04:00
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-23 00:30:26 -04:00
pub fn citation < ' r , ' s > (
2023-09-02 22:45:46 -04:00
context : RefContext < '_ , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , Citation < ' s > > {
2023-07-21 18:19:39 -04:00
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
2023-07-21 17:52:18 -04:00
let ( remaining , _ ) = tag_no_case ( " [cite " ) ( input ) ? ;
let ( remaining , _ ) = opt ( citestyle ) ( remaining ) ? ;
let ( remaining , _ ) = tag ( " : " ) ( remaining ) ? ;
2023-08-28 00:34:48 -04:00
let ( remaining , _prefix ) =
must_balance_bracket ( opt ( parser_with_context! ( global_prefix ) ( context ) ) ) ( remaining ) ? ;
2023-07-21 17:52:18 -04:00
let ( remaining , _references ) =
2023-07-21 18:42:22 -04:00
separated_list1 ( tag ( " ; " ) , parser_with_context! ( citation_reference ) ( context ) ) ( remaining ) ? ;
2023-08-28 00:34:48 -04:00
let ( remaining , _suffix ) = must_balance_bracket ( opt ( tuple ( (
2023-07-21 18:42:22 -04:00
tag ( " ; " ) ,
parser_with_context! ( global_suffix ) ( context ) ,
2023-08-28 00:34:48 -04:00
) ) ) ) ( remaining ) ? ;
2023-07-21 17:52:18 -04:00
let ( remaining , _ ) = tag ( " ] " ) ( remaining ) ? ;
2023-08-31 15:44:44 -04:00
let ( remaining , _trailing_whitespace ) =
maybe_consume_object_trailing_whitespace_if_not_exiting ( context , remaining ) ? ;
2023-07-21 17:52:18 -04:00
let source = get_consumed ( input , remaining ) ;
2023-08-23 00:30:26 -04:00
Ok ( (
remaining ,
Citation {
source : source . into ( ) ,
} ,
) )
2023-07-21 17:52:18 -04:00
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-23 00:30:26 -04:00
fn citestyle < ' r , ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-07-21 17:52:18 -04:00
let ( remaining , _ ) = tuple ( ( tag ( " / " ) , style ) ) ( input ) ? ;
let ( remaining , _ ) = opt ( tuple ( ( tag ( " / " ) , variant ) ) ) ( remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
Ok ( ( remaining , source ) )
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-23 00:30:26 -04:00
fn style < ' r , ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-07-21 17:52:18 -04:00
recognize ( many1 ( verify ( anychar , | c | {
c . is_alphanumeric ( ) | | " _- " . contains ( * c )
} ) ) ) ( input )
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-23 00:30:26 -04:00
fn variant < ' r , ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-07-21 17:52:18 -04:00
recognize ( many1 ( verify ( anychar , | c | {
c . is_alphanumeric ( ) | | " _-/ " . contains ( * c )
} ) ) ) ( input )
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-07-21 17:52:18 -04:00
fn global_prefix < ' r , ' s > (
2023-09-02 22:45:46 -04:00
context : RefContext < '_ , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , Vec < Object < ' s > > > {
2023-08-28 00:34:48 -04:00
let exit_with_depth = global_prefix_end ( input . get_bracket_depth ( ) ) ;
let parser_context =
context . with_additional_node ( ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-08-24 23:34:23 -04:00
class : ExitClass ::Gamma ,
2023-08-28 00:34:48 -04:00
exit_matcher : & exit_with_depth ,
2023-07-21 17:52:18 -04:00
} ) ) ;
let ( remaining , ( children , _exit_contents ) ) = verify (
many_till (
parser_with_context! ( standard_set_object ) ( & parser_context ) ,
parser_with_context! ( exit_matcher_parser ) ( & parser_context ) ,
) ,
| ( children , _exit_contents ) | ! children . is_empty ( ) ,
) ( input ) ? ;
2023-07-21 18:42:22 -04:00
let ( remaining , _ ) = tag ( " ; " ) ( remaining ) ? ;
2023-07-21 17:52:18 -04:00
Ok ( ( remaining , children ) )
}
2023-08-28 00:34:48 -04:00
fn global_prefix_end (
2023-08-29 11:18:15 -04:00
starting_bracket_depth : BracketDepth ,
2023-08-28 00:34:48 -04:00
) -> impl for < ' r , ' s > Fn ( Context < ' r , ' s > , OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
move | context : Context , input : OrgSource < '_ > | {
_global_prefix_end ( context , input , starting_bracket_depth )
}
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-28 00:34:48 -04:00
fn _global_prefix_end < ' r , ' s > (
2023-09-02 22:45:46 -04:00
context : RefContext < '_ , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
2023-08-29 11:18:15 -04:00
starting_bracket_depth : BracketDepth ,
2023-08-23 00:30:26 -04:00
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-08-28 00:34:48 -04:00
let current_depth = input . get_bracket_depth ( ) - starting_bracket_depth ;
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
unreachable! ( " Exceeded citation global prefix bracket depth. " )
2023-07-21 17:52:18 -04:00
}
if current_depth = = 0 {
2023-08-23 00:30:26 -04:00
let close_bracket = tag ::< & str , OrgSource < '_ > , CustomError < OrgSource < '_ > > > ( " ] " ) ( input ) ;
2023-07-21 17:52:18 -04:00
if close_bracket . is_ok ( ) {
return close_bracket ;
}
}
alt ( (
tag ( " ; " ) ,
recognize ( parser_with_context! ( citation_reference_key ) ( context ) ) ,
) ) ( input )
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-07-21 17:52:18 -04:00
fn global_suffix < ' r , ' s > (
2023-09-02 22:45:46 -04:00
context : RefContext < '_ , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , Vec < Object < ' s > > > {
2023-08-28 00:34:48 -04:00
let exit_with_depth = global_suffix_end ( input . get_bracket_depth ( ) ) ;
let parser_context =
context . with_additional_node ( ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-08-24 23:34:23 -04:00
class : ExitClass ::Gamma ,
2023-08-28 00:34:48 -04:00
exit_matcher : & exit_with_depth ,
2023-07-21 17:52:18 -04:00
} ) ) ;
let ( remaining , ( children , _exit_contents ) ) = verify (
many_till (
parser_with_context! ( standard_set_object ) ( & parser_context ) ,
parser_with_context! ( exit_matcher_parser ) ( & parser_context ) ,
) ,
| ( children , _exit_contents ) | ! children . is_empty ( ) ,
) ( input ) ? ;
Ok ( ( remaining , children ) )
}
2023-08-28 00:34:48 -04:00
fn global_suffix_end (
2023-08-29 11:18:15 -04:00
starting_bracket_depth : BracketDepth ,
2023-08-28 00:34:48 -04:00
) -> impl for < ' r , ' s > Fn ( Context < ' r , ' s > , OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
move | context : Context , input : OrgSource < '_ > | {
_global_suffix_end ( context , input , starting_bracket_depth )
}
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-08-28 00:34:48 -04:00
fn _global_suffix_end < ' r , ' s > (
2023-09-02 22:45:46 -04:00
context : RefContext < '_ , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
2023-08-29 11:18:15 -04:00
starting_bracket_depth : BracketDepth ,
2023-08-23 00:30:26 -04:00
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-08-28 00:34:48 -04:00
let current_depth = input . get_bracket_depth ( ) - starting_bracket_depth ;
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
unreachable! ( " Exceeded citation global suffix bracket depth. " )
2023-07-21 17:52:18 -04:00
}
if current_depth = = 0 {
2023-08-23 00:30:26 -04:00
let close_bracket = tag ::< & str , OrgSource < '_ > , CustomError < OrgSource < '_ > > > ( " ] " ) ( input ) ;
2023-07-21 17:52:18 -04:00
if close_bracket . is_ok ( ) {
return close_bracket ;
}
}
alt ( (
tag ( " ; " ) ,
recognize ( parser_with_context! ( citation_reference_key ) ( context ) ) ,
) ) ( input )
2023-07-20 00:38:16 -04:00
}
2023-07-21 18:00:19 -04:00
#[ cfg(test) ]
mod tests {
use super ::* ;
use crate ::parser ::element_parser ::element ;
use crate ::parser ::parser_context ::ContextTree ;
use crate ::parser ::parser_with_context ::parser_with_context ;
use crate ::parser ::source ::Source ;
#[ test ]
fn citation_simple ( ) {
2023-08-24 17:01:12 -04:00
let input = OrgSource ::new ( " [cite:@foo] " ) ;
2023-07-21 18:00:19 -04:00
let initial_context : ContextTree < '_ , '_ > = ContextTree ::new ( ) ;
2023-08-24 17:01:12 -04:00
let paragraph_matcher = parser_with_context! ( element ( true ) ) ( & initial_context ) ;
2023-07-21 18:00:19 -04:00
let ( remaining , first_paragraph ) = paragraph_matcher ( input ) . expect ( " Parse first paragraph " ) ;
let first_paragraph = match first_paragraph {
crate ::parser ::Element ::Paragraph ( paragraph ) = > paragraph ,
_ = > panic! ( " Should be a paragraph! " ) ,
} ;
2023-08-24 17:01:12 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " " ) ;
2023-07-21 18:00:19 -04:00
assert_eq! ( first_paragraph . get_source ( ) , " [cite:@foo] " ) ;
assert_eq! ( first_paragraph . children . len ( ) , 1 ) ;
assert_eq! (
first_paragraph
. children
. get ( 0 )
. expect ( " Len already asserted to be 1 " ) ,
& Object ::Citation ( Citation {
source : " [cite:@foo] "
} )
) ;
}
}