2023-03-25 14:10:22 -04:00
use nom ::branch ::alt ;
2023-03-25 14:23:52 -04:00
use nom ::bytes ::complete ::tag ;
2023-09-11 10:29:15 -04:00
use nom ::character ::complete ::anychar ;
2023-03-25 14:23:52 -04:00
use nom ::character ::complete ::digit1 ;
2023-04-03 16:29:47 -04:00
use nom ::character ::complete ::line_ending ;
2023-03-25 14:23:52 -04:00
use nom ::character ::complete ::one_of ;
2023-03-25 14:10:22 -04:00
use nom ::character ::complete ::space0 ;
2023-04-03 16:29:47 -04:00
use nom ::character ::complete ::space1 ;
2023-03-25 14:10:22 -04:00
use nom ::combinator ::eof ;
2023-09-15 15:30:13 -04:00
use nom ::combinator ::map ;
2023-08-29 20:11:23 -04:00
use nom ::combinator ::not ;
2023-08-16 17:09:06 -04:00
use nom ::combinator ::opt ;
2023-08-29 20:11:23 -04:00
use nom ::combinator ::peek ;
2023-03-25 14:10:22 -04:00
use nom ::combinator ::recognize ;
use nom ::combinator ::verify ;
2023-08-29 20:11:23 -04:00
use nom ::multi ::many0 ;
2023-04-12 13:09:44 -04:00
use nom ::multi ::many1 ;
2023-03-27 12:52:49 -04:00
use nom ::multi ::many_till ;
2023-03-25 14:10:22 -04:00
use nom ::sequence ::tuple ;
2023-09-03 00:05:47 -04:00
use super ::element_parser ::element ;
use super ::object_parser ::standard_set_object ;
2023-08-23 00:30:26 -04:00
use super ::org_source ::OrgSource ;
2023-09-08 21:30:03 -04:00
use super ::util ::include_input ;
2023-09-15 21:52:42 -04:00
use super ::util ::indentation_level ;
2023-04-22 21:45:18 -04:00
use super ::util ::non_whitespace_character ;
2023-09-03 00:05:47 -04:00
use crate ::context ::parser_with_context ;
use crate ::context ::ContextElement ;
2023-09-03 15:44:18 -04:00
use crate ::context ::ContextMatcher ;
2023-09-03 00:05:47 -04:00
use crate ::context ::ExitClass ;
use crate ::context ::ExitMatcherNode ;
use crate ::context ::RefContext ;
2023-04-22 21:45:18 -04:00
use crate ::error ::CustomError ;
use crate ::error ::MyError ;
use crate ::error ::Res ;
use crate ::parser ::util ::blank_line ;
use crate ::parser ::util ::exit_matcher_parser ;
use crate ::parser ::util ::get_consumed ;
2023-08-16 17:09:06 -04:00
use crate ::parser ::util ::maybe_consume_trailing_whitespace_if_not_exiting ;
2023-09-15 15:30:13 -04:00
use crate ::parser ::util ::org_space ;
2023-04-22 21:45:18 -04:00
use crate ::parser ::util ::start_of_line ;
2023-09-15 15:30:13 -04:00
use crate ::types ::CheckboxType ;
2023-09-15 21:52:42 -04:00
use crate ::types ::IndentationLevel ;
2023-09-03 00:05:47 -04:00
use crate ::types ::Object ;
use crate ::types ::PlainList ;
use crate ::types ::PlainListItem ;
2023-04-22 21:45:18 -04:00
2023-08-25 00:48:34 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-14 00:27:54 -04:00
pub ( crate ) fn detect_plain_list < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , ( ) > {
2023-08-25 00:48:34 -04:00
if verify (
2023-08-25 05:20:13 -04:00
tuple ( (
start_of_line ,
space0 ,
2023-09-14 00:27:54 -04:00
parser_with_context! ( bullet ) ( context ) ,
2023-08-25 05:20:13 -04:00
alt ( ( space1 , line_ending , eof ) ) ,
) ) ,
2023-08-25 00:48:34 -04:00
| ( _start , indent , bull , _after_whitespace ) | {
Into ::< & str > ::into ( bull ) ! = " * " | | indent . len ( ) > 0
} ,
) ( input )
. is_ok ( )
{
return Ok ( ( input , ( ) ) ) ;
}
return Err ( nom ::Err ::Error ( CustomError ::MyError ( MyError (
" No element detected. " . into ( ) ,
) ) ) ) ;
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-11 13:13:28 -04:00
pub ( crate ) fn plain_list < ' b , ' g , ' r , ' s > (
2023-09-03 15:44:18 -04:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , PlainList < ' s > > {
2023-09-03 00:05:47 -04:00
let contexts = [
ContextElement ::Context ( " plain list " ) ,
ContextElement ::ConsumeTrailingWhitespace ( true ) ,
ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-04-18 20:33:01 -04:00
class : ExitClass ::Beta ,
exit_matcher : & plain_list_end ,
2023-09-03 00:05:47 -04:00
} ) ,
] ;
2023-09-03 12:28:45 -04:00
let parser_context = context . with_additional_node ( & contexts [ 0 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 1 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 2 ] ) ;
2023-08-16 17:37:19 -04:00
// children stores tuple of (input string, parsed object) so we can re-parse the final item
2023-04-03 19:30:17 -04:00
let mut children = Vec ::new ( ) ;
2023-09-15 21:52:42 -04:00
let mut first_item_indentation : Option < IndentationLevel > = None ;
2023-04-14 19:24:05 -04:00
let mut remaining = input ;
2023-08-16 17:37:19 -04:00
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
//
// 1. Parse all items while consuming trailing whitespace, then edit the final item to remove trailing whitespace.
// 2. Parse all items without consuming trailing whitespace, then edit all but the final one to add in the trailing whitespace.
// 3. Re-parse the final item with consume trailing whitespace disabled.
//
// While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed.
2023-04-03 19:30:17 -04:00
loop {
2023-08-15 01:28:19 -04:00
let list_item = parser_with_context! ( plain_list_item ) ( & parser_context ) ( remaining ) ;
match list_item {
Ok ( ( remain , item ) )
if item . indentation = = * first_item_indentation . get_or_insert ( item . indentation ) = >
{
2023-08-16 17:37:19 -04:00
children . push ( ( remaining , item ) ) ;
2023-08-15 01:28:19 -04:00
remaining = remain ;
}
Ok ( _ ) | Err ( _ ) = > {
break ;
}
} ;
2023-08-16 17:09:06 -04:00
let maybe_exit = parser_with_context! ( exit_matcher_parser ) ( & parser_context ) ( remaining ) ;
if maybe_exit . is_ok ( ) {
break ;
}
2023-04-03 19:30:17 -04:00
}
2023-04-10 11:50:43 -04:00
2023-08-16 17:37:19 -04:00
let ( final_child_start , _final_item_first_parse ) = match children . pop ( ) {
Some ( final_child ) = > final_child ,
None = > {
return Err ( nom ::Err ::Error ( CustomError ::MyError ( MyError (
2023-08-23 00:30:26 -04:00
" Plain lists require at least one element. " . into ( ) ,
2023-08-16 17:37:19 -04:00
) ) ) ) ;
}
} ;
2023-09-03 00:05:47 -04:00
let final_item_context = ContextElement ::ConsumeTrailingWhitespace ( false ) ;
let final_item_context = parser_context . with_additional_node ( & final_item_context ) ;
2023-08-16 17:37:19 -04:00
let ( remaining , reparsed_final_item ) =
parser_with_context! ( plain_list_item ) ( & final_item_context ) ( final_child_start ) ? ;
children . push ( ( final_child_start , reparsed_final_item ) ) ;
2023-04-14 19:24:05 -04:00
2023-03-27 12:52:49 -04:00
let source = get_consumed ( input , remaining ) ;
2023-08-16 17:37:19 -04:00
Ok ( (
remaining ,
PlainList {
2023-08-23 00:30:26 -04:00
source : source . into ( ) ,
2023-08-16 17:37:19 -04:00
children : children . into_iter ( ) . map ( | ( _start , item ) | item ) . collect ( ) ,
} ,
) )
2023-03-25 14:28:48 -04:00
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-11 13:13:28 -04:00
fn plain_list_item < ' b , ' g , ' r , ' s > (
2023-09-03 15:44:18 -04:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , PlainListItem < ' s > > {
2023-08-24 19:29:00 -04:00
start_of_line ( input ) ? ;
2023-09-15 21:52:42 -04:00
let ( remaining , ( indent_level , _leading_whitespace ) ) = indentation_level ( context , input ) ? ;
2023-09-14 00:27:54 -04:00
let ( remaining , bull ) = verify (
parser_with_context! ( bullet ) ( context ) ,
| bull : & OrgSource < '_ > | Into ::< & str > ::into ( bull ) ! = " * " | | indent_level > 0 ,
) ( remaining ) ? ;
2023-08-15 02:02:15 -04:00
2023-09-14 00:27:54 -04:00
let ( remaining , _maybe_counter_set ) = opt ( tuple ( (
space1 ,
tag ( " [@ " ) ,
parser_with_context! ( counter ) ( context ) ,
tag ( " ] " ) ,
) ) ) ( remaining ) ? ;
2023-09-06 19:09:20 -04:00
2023-09-15 15:30:13 -04:00
let ( remaining , maybe_checkbox ) = opt ( tuple ( ( space1 , item_checkbox ) ) ) ( remaining ) ? ;
2023-09-06 19:09:20 -04:00
2023-09-08 14:02:15 -04:00
let ( remaining , maybe_tag ) =
opt ( tuple ( ( space1 , parser_with_context! ( item_tag ) ( context ) ) ) ) ( remaining ) ? ;
2023-09-08 19:01:46 -04:00
let maybe_contentless_item : Res < OrgSource < '_ > , ( ) > = peek ( parser_with_context! (
detect_contentless_item_contents
) ( context ) ) ( remaining ) ;
2023-04-03 16:29:47 -04:00
match maybe_contentless_item {
2023-08-31 17:25:42 -04:00
Ok ( ( _rem , _ws ) ) = > {
let ( remaining , _trailing_ws ) = opt ( blank_line ) ( remaining ) ? ;
let source = get_consumed ( input , remaining ) ;
2023-04-03 16:29:47 -04:00
return Ok ( (
2023-08-31 17:25:42 -04:00
remaining ,
2023-04-03 16:29:47 -04:00
PlainListItem {
2023-08-23 00:30:26 -04:00
source : source . into ( ) ,
2023-04-03 16:29:47 -04:00
indentation : indent_level ,
2023-08-23 00:30:26 -04:00
bullet : bull . into ( ) ,
2023-09-15 15:30:13 -04:00
checkbox : None ,
2023-08-31 17:25:42 -04:00
tag : maybe_tag
2023-09-08 14:02:15 -04:00
. map ( | ( _ws , item_tag ) | item_tag )
2023-08-31 17:25:42 -04:00
. unwrap_or ( Vec ::new ( ) ) ,
2023-04-03 17:33:01 -04:00
children : Vec ::new ( ) ,
2023-04-03 16:29:47 -04:00
} ,
) ) ;
}
2023-08-15 02:02:15 -04:00
Err ( _ ) = > { }
2023-04-03 16:29:47 -04:00
} ;
2023-08-29 20:11:23 -04:00
let ( remaining , _ws ) = item_tag_post_gap ( context , remaining ) ? ;
2023-08-24 20:37:58 -04:00
let exit_matcher = plain_list_item_end ( indent_level ) ;
2023-09-03 00:05:47 -04:00
let contexts = [
ContextElement ::ConsumeTrailingWhitespace ( true ) ,
ContextElement ::ExitMatcherNode ( ExitMatcherNode {
2023-08-15 02:02:15 -04:00
class : ExitClass ::Beta ,
2023-08-24 20:37:58 -04:00
exit_matcher : & exit_matcher ,
2023-09-03 00:05:47 -04:00
} ) ,
] ;
2023-09-03 12:28:45 -04:00
let parser_context = context . with_additional_node ( & contexts [ 0 ] ) ;
let parser_context = parser_context . with_additional_node ( & contexts [ 1 ] ) ;
2023-08-15 02:02:15 -04:00
2023-08-31 17:08:21 -04:00
let ( mut remaining , ( mut children , _exit_contents ) ) = many_till (
include_input ( parser_with_context! ( element ( true ) ) ( & parser_context ) ) ,
parser_with_context! ( exit_matcher_parser ) ( & parser_context ) ,
2023-08-15 02:02:15 -04:00
) ( remaining ) ? ;
2023-08-31 17:08:21 -04:00
if ! children . is_empty ( ) & & ! context . should_consume_trailing_whitespace ( ) {
2023-09-03 00:05:47 -04:00
let final_item_context = ContextElement ::ConsumeTrailingWhitespace ( false ) ;
let final_item_context = parser_context . with_additional_node ( & final_item_context ) ;
2023-08-31 17:25:42 -04:00
let ( final_child_start , _original_final_child ) = children
. pop ( )
. expect ( " if-statement already checked that children was non-empty. " ) ;
let ( remain , reparsed_final_element ) = include_input ( parser_with_context! ( element ( true ) ) (
& final_item_context ,
) ) ( final_child_start ) ? ;
2023-08-31 17:08:21 -04:00
remaining = remain ;
children . push ( reparsed_final_element ) ;
}
2023-08-16 17:09:06 -04:00
let ( remaining , _trailing_ws ) =
maybe_consume_trailing_whitespace_if_not_exiting ( context , remaining ) ? ;
2023-08-15 02:02:15 -04:00
let source = get_consumed ( input , remaining ) ;
return Ok ( (
remaining ,
PlainListItem {
2023-08-23 00:30:26 -04:00
source : source . into ( ) ,
2023-08-15 02:02:15 -04:00
indentation : indent_level ,
2023-08-23 00:30:26 -04:00
bullet : bull . into ( ) ,
2023-09-15 15:30:13 -04:00
checkbox : maybe_checkbox
. map ( | ( _ , ( checkbox_type , source ) ) | ( checkbox_type , Into ::< & str > ::into ( source ) ) ) ,
2023-08-29 19:22:11 -04:00
tag : maybe_tag
2023-09-08 14:02:15 -04:00
. map ( | ( _ws , item_tag ) | item_tag )
2023-08-29 20:11:23 -04:00
. unwrap_or ( Vec ::new ( ) ) ,
2023-08-31 17:08:21 -04:00
children : children . into_iter ( ) . map ( | ( _start , item ) | item ) . collect ( ) ,
2023-08-15 02:02:15 -04:00
} ,
) ) ;
2023-03-25 14:23:52 -04:00
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-14 00:27:54 -04:00
fn bullet < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-03-25 14:23:52 -04:00
alt ( (
tag ( " * " ) ,
tag ( " - " ) ,
tag ( " + " ) ,
2023-09-14 00:27:54 -04:00
recognize ( tuple ( (
parser_with_context! ( counter ) ( context ) ,
alt ( ( tag ( " . " ) , tag ( " ) " ) ) ) ,
) ) ) ,
) ) ( input )
2023-03-25 14:23:52 -04:00
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-14 00:27:54 -04:00
fn counter < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
if context . get_global_settings ( ) . org_list_allow_alphabetical {
alt ( (
recognize ( one_of (
" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ " ,
) ) ,
digit1 ,
) ) ( input )
} else {
digit1 ( input )
}
2023-03-25 13:25:20 -04:00
}
2023-03-25 14:10:22 -04:00
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 15:44:18 -04:00
fn plain_list_end < ' b , ' g , ' r , ' s > (
_context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-04-12 13:09:44 -04:00
recognize ( tuple ( (
2023-08-24 19:29:00 -04:00
start_of_line ,
2023-08-23 00:30:26 -04:00
verify ( many1 ( blank_line ) , | lines : & Vec < OrgSource < '_ > > | {
lines . len ( ) > = 2
} ) ,
2023-04-12 13:09:44 -04:00
) ) ) ( input )
}
2023-09-15 21:52:42 -04:00
const fn plain_list_item_end ( indent_level : IndentationLevel ) -> impl ContextMatcher {
2023-08-24 20:37:58 -04:00
let line_indented_lte_matcher = line_indented_lte ( indent_level ) ;
2023-09-03 00:05:47 -04:00
move | context , input : OrgSource < '_ > | {
2023-08-24 20:37:58 -04:00
_plain_list_item_end ( context , input , & line_indented_lte_matcher )
}
}
#[ cfg_attr(
feature = " tracing " ,
tracing ::instrument ( ret , level = " debug " , skip ( line_indented_lte_matcher ) )
) ]
2023-09-03 15:44:18 -04:00
fn _plain_list_item_end < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
2023-09-03 15:44:18 -04:00
line_indented_lte_matcher : impl ContextMatcher ,
2023-08-23 00:30:26 -04:00
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-08-24 19:29:00 -04:00
start_of_line ( input ) ? ;
2023-08-16 17:09:06 -04:00
recognize ( tuple ( (
opt ( blank_line ) ,
2023-08-24 20:37:58 -04:00
parser_with_context! ( line_indented_lte_matcher ) ( context ) ,
2023-08-16 17:09:06 -04:00
) ) ) ( input )
2023-03-25 14:10:22 -04:00
}
2023-09-15 21:52:42 -04:00
const fn line_indented_lte ( indent_level : IndentationLevel ) -> impl ContextMatcher {
2023-09-03 00:05:47 -04:00
move | context , input : OrgSource < '_ > | _line_indented_lte ( context , input , indent_level )
2023-08-24 20:37:58 -04:00
}
2023-08-10 20:04:59 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 15:44:18 -04:00
fn _line_indented_lte < ' b , ' g , ' r , ' s > (
2023-09-15 21:52:42 -04:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 00:30:26 -04:00
input : OrgSource < ' s > ,
2023-09-15 21:52:42 -04:00
indent_level : IndentationLevel ,
2023-08-23 00:30:26 -04:00
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-03-25 14:10:22 -04:00
let matched = recognize ( verify (
2023-09-15 21:52:42 -04:00
tuple ( (
parser_with_context! ( indentation_level ) ( context ) ,
non_whitespace_character ,
) ) ,
2023-03-25 14:10:22 -04:00
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
2023-09-15 21:52:42 -04:00
| ( ( indentation_level , _leading_whitespace ) , _anychar ) | * indentation_level < = indent_level ,
2023-03-25 14:10:22 -04:00
) ) ( input ) ? ;
Ok ( matched )
}
2023-08-29 18:07:29 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 15:44:18 -04:00
fn item_tag < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-29 19:22:11 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , Vec < Object < ' s > > > {
2023-09-11 10:13:22 -04:00
let parser_context = ContextElement ::ExitMatcherNode ( ExitMatcherNode {
class : ExitClass ::Gamma ,
exit_matcher : & item_tag_end ,
} ) ;
let parser_context = context . with_additional_node ( & parser_context ) ;
2023-08-29 19:22:11 -04:00
let ( remaining , ( children , _exit_contents ) ) = verify (
many_till (
// TODO: Should this be using a different set like the minimal set?
parser_with_context! ( standard_set_object ) ( & parser_context ) ,
parser_with_context! ( exit_matcher_parser ) ( & parser_context ) ,
) ,
| ( children , _exit_contents ) | ! children . is_empty ( ) ,
) ( input ) ? ;
2023-09-11 10:29:15 -04:00
let ( remaining , _ ) = item_tag_divider ( remaining ) ? ;
2023-08-29 19:22:11 -04:00
Ok ( ( remaining , children ) )
}
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 15:44:18 -04:00
fn item_tag_end < ' b , ' g , ' r , ' s > (
_context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-29 19:22:11 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
2023-09-19 23:57:40 -04:00
alt ( ( item_tag_divider , line_ending ) ) ( input )
}
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
fn item_tag_divider < ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
recognize ( tuple ( (
one_of ( " \t " ) ,
tag ( " :: " ) ,
peek ( tuple ( (
2023-09-11 10:29:15 -04:00
opt ( tuple ( (
peek ( one_of ( " \t " ) ) ,
many_till ( anychar , peek ( alt ( ( item_tag_divider , line_ending , eof ) ) ) ) ,
) ) ) ,
alt ( ( line_ending , eof ) ) ,
2023-09-11 10:13:22 -04:00
) ) ) ,
2023-09-19 23:57:40 -04:00
) ) ) ( input )
2023-09-11 10:29:15 -04:00
}
2023-08-29 20:11:23 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 15:44:18 -04:00
fn item_tag_post_gap < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-29 20:11:23 -04:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , OrgSource < ' s > > {
verify (
recognize ( tuple ( (
alt ( ( blank_line , space0 ) ) ,
many_till (
blank_line ,
alt ( (
peek ( recognize ( not ( blank_line ) ) ) ,
peek ( recognize ( tuple ( ( many0 ( blank_line ) , eof ) ) ) ) ,
parser_with_context! ( exit_matcher_parser ) ( context ) ,
) ) ,
) ,
) ) ) ,
| gap | gap . len ( ) > 0 ,
) ( input )
}
2023-09-15 15:30:13 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
fn item_checkbox < ' s > ( input : OrgSource < ' s > ) -> Res < OrgSource < ' s > , ( CheckboxType , OrgSource < ' s > ) > {
alt ( (
map (
recognize ( tuple ( ( tag ( " [ " ) , org_space , tag ( " ] " ) ) ) ) ,
| capture | ( CheckboxType ::Off , capture ) ,
) ,
map ( tag ( " [-] " ) , | capture | ( CheckboxType ::Trans , capture ) ) ,
map ( tag ( " [X] " ) , | capture | ( CheckboxType ::On , capture ) ) ,
) ) ( input )
}
2023-09-08 19:01:46 -04:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
fn detect_contentless_item_contents < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , ( ) > {
let ( remaining , _ ) = recognize ( many_till (
blank_line ,
parser_with_context! ( exit_matcher_parser ) ( context ) ,
) ) ( input ) ? ;
Ok ( ( remaining , ( ) ) )
}
2023-03-25 14:28:48 -04:00
#[ cfg(test) ]
mod tests {
2023-04-22 21:45:18 -04:00
use super ::* ;
2023-09-03 00:05:47 -04:00
use crate ::context ::Context ;
use crate ::context ::GlobalSettings ;
use crate ::context ::List ;
2023-09-03 12:23:18 -04:00
use crate ::types ::Source ;
2023-03-25 14:28:48 -04:00
#[ test ]
fn plain_list_item_empty ( ) {
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new ( " 1. " ) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_item_matcher = parser_with_context! ( plain_list_item ) ( & initial_context ) ;
2023-03-25 14:28:48 -04:00
let ( remaining , result ) = plain_list_item_matcher ( input ) . unwrap ( ) ;
2023-08-24 17:15:24 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " " ) ;
2023-03-25 14:28:48 -04:00
assert_eq! ( result . source , " 1. " ) ;
}
2023-03-25 14:45:35 -04:00
#[ test ]
fn plain_list_item_simple ( ) {
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new ( " 1. foo " ) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_item_matcher = parser_with_context! ( plain_list_item ) ( & initial_context ) ;
2023-03-25 14:45:35 -04:00
let ( remaining , result ) = plain_list_item_matcher ( input ) . unwrap ( ) ;
2023-08-24 17:15:24 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " " ) ;
2023-03-25 14:45:35 -04:00
assert_eq! ( result . source , " 1. foo " ) ;
}
2023-03-27 12:52:49 -04:00
#[ test ]
fn plain_list_empty ( ) {
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new ( " 1. " ) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_matcher = parser_with_context! ( plain_list ) ( & initial_context ) ;
2023-03-27 12:52:49 -04:00
let ( remaining , result ) = plain_list_matcher ( input ) . unwrap ( ) ;
2023-08-24 17:15:24 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " " ) ;
2023-03-27 12:52:49 -04:00
assert_eq! ( result . source , " 1. " ) ;
}
#[ test ]
fn plain_list_simple ( ) {
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new ( " 1. foo " ) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_matcher = parser_with_context! ( plain_list ) ( & initial_context ) ;
2023-03-27 12:52:49 -04:00
let ( remaining , result ) = plain_list_matcher ( input ) . unwrap ( ) ;
2023-08-24 17:15:24 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " " ) ;
2023-03-27 12:52:49 -04:00
assert_eq! ( result . source , " 1. foo " ) ;
}
2023-04-03 16:38:26 -04:00
#[ test ]
fn plain_list_cant_start_line_with_asterisk ( ) {
// Plain lists with an asterisk bullet must be indented or else they would be a headline
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new ( " * foo " ) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_matcher = parser_with_context! ( plain_list ) ( & initial_context ) ;
2023-04-03 16:38:26 -04:00
let result = plain_list_matcher ( input ) ;
assert! ( result . is_err ( ) ) ;
}
#[ test ]
fn indented_can_start_line_with_asterisk ( ) {
// Plain lists with an asterisk bullet must be indented or else they would be a headline
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new ( " * foo " ) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_matcher = parser_with_context! ( plain_list ) ( & initial_context ) ;
2023-04-03 16:38:26 -04:00
let result = plain_list_matcher ( input ) ;
assert! ( result . is_ok ( ) ) ;
}
2023-04-12 13:09:44 -04:00
#[ test ]
fn two_blank_lines_ends_list ( ) {
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new (
r #" 1. foo
2023-04-12 13:09:44 -04:00
2. bar
baz
3. lorem
ipsum
2023-08-24 17:15:24 -04:00
" #,
) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_matcher = parser_with_context! ( element ( true ) ) ( & initial_context ) ;
2023-04-12 13:09:44 -04:00
let ( remaining , result ) =
plain_list_matcher ( input ) . expect ( " Should parse the plain list successfully. " ) ;
2023-08-24 17:15:24 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " ipsum \n " ) ;
2023-04-12 13:09:44 -04:00
assert_eq! (
result . get_source ( ) ,
r #" 1. foo
2. bar
baz
3. lorem
2023-04-12 13:21:31 -04:00
" #
) ;
}
#[ test ]
fn two_blank_lines_ends_nested_list ( ) {
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new (
r #" 1. foo
2023-04-12 13:21:31 -04:00
1. bar
2023-08-24 17:15:24 -04:00
baz " #,
) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_matcher = parser_with_context! ( element ( true ) ) ( & initial_context ) ;
2023-04-12 13:21:31 -04:00
let ( remaining , result ) =
plain_list_matcher ( input ) . expect ( " Should parse the plain list successfully. " ) ;
2023-08-24 17:15:24 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " baz " ) ;
2023-04-12 13:21:31 -04:00
assert_eq! (
result . get_source ( ) ,
r #" 1. foo
1. bar
2023-04-12 15:24:18 -04:00
" #
) ;
}
#[ test ]
fn interior_trailing_whitespace ( ) {
2023-08-24 17:15:24 -04:00
let input = OrgSource ::new (
r #" 1. foo
2023-04-12 15:24:18 -04:00
bar
1. baz
lorem
2023-04-14 19:24:05 -04:00
ipsum
2023-04-12 15:24:18 -04:00
2023-08-24 17:15:24 -04:00
dolar " #,
) ;
2023-09-03 00:05:47 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
2023-08-24 17:15:24 -04:00
let plain_list_matcher = parser_with_context! ( element ( true ) ) ( & initial_context ) ;
2023-04-12 15:24:18 -04:00
let ( remaining , result ) =
plain_list_matcher ( input ) . expect ( " Should parse the plain list successfully. " ) ;
2023-08-24 17:15:24 -04:00
assert_eq! ( Into ::< & str > ::into ( remaining ) , " dolar " ) ;
2023-04-12 15:24:18 -04:00
assert_eq! (
result . get_source ( ) ,
r #" 1. foo
bar
1. baz
lorem
2023-04-14 19:24:05 -04:00
ipsum
2023-04-12 15:24:18 -04:00
2023-04-12 13:09:44 -04:00
" #
) ;
}
2023-08-25 05:25:41 -04:00
#[ test ]
fn detect_line_break ( ) {
let input = OrgSource ::new (
r #" +
" #,
) ;
2023-09-14 00:27:54 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
let result = detect_plain_list ( & initial_context , input ) ;
2023-08-25 05:25:41 -04:00
assert! ( result . is_ok ( ) ) ;
}
#[ test ]
fn detect_eof ( ) {
let input = OrgSource ::new ( r # "+"# ) ;
2023-09-14 00:27:54 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
let result = detect_plain_list ( & initial_context , input ) ;
2023-08-25 05:25:41 -04:00
assert! ( result . is_ok ( ) ) ;
}
#[ test ]
fn detect_no_gap ( ) {
let input = OrgSource ::new ( r # "+foo"# ) ;
2023-09-14 00:27:54 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
let result = detect_plain_list ( & initial_context , input ) ;
2023-08-25 05:25:41 -04:00
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert! ( result . is_err ( ) ) ;
}
#[ test ]
fn detect_with_gap ( ) {
let input = OrgSource ::new ( r # "+ foo"# ) ;
2023-09-14 00:27:54 -04:00
let global_settings = GlobalSettings ::default ( ) ;
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( & global_settings , List ::new ( & initial_context ) ) ;
let result = detect_plain_list ( & initial_context , input ) ;
2023-08-25 05:25:41 -04:00
assert! ( result . is_ok ( ) ) ;
}
2023-03-25 14:28:48 -04:00
}