2023-09-30 01:46:52 +00:00
use std ::path ::Path ;
2023-09-03 19:44:18 +00:00
use nom ::combinator ::all_consuming ;
2023-03-24 21:34:56 +00:00
use nom ::combinator ::opt ;
2023-03-24 21:19:46 +00:00
use nom ::multi ::many0 ;
2023-03-23 23:35:32 +00:00
2023-09-08 19:05:42 +00:00
use super ::headline ::heading ;
2023-09-06 15:00:19 +00:00
use super ::in_buffer_settings ::apply_in_buffer_settings ;
2023-10-02 14:03:04 +00:00
use super ::in_buffer_settings ::apply_post_parse_in_buffer_settings ;
2023-09-04 23:17:23 +00:00
use super ::in_buffer_settings ::scan_for_in_buffer_settings ;
2023-08-23 02:33:50 +00:00
use super ::org_source ::OrgSource ;
2023-09-08 19:08:16 +00:00
use super ::section ::zeroth_section ;
2023-03-25 15:25:10 +00:00
use super ::util ::get_consumed ;
2023-09-03 04:05:47 +00:00
use crate ::context ::parser_with_context ;
use crate ::context ::Context ;
use crate ::context ::ContextElement ;
use crate ::context ::GlobalSettings ;
use crate ::context ::List ;
use crate ::context ::RefContext ;
2023-09-04 20:53:02 +00:00
use crate ::error ::CustomError ;
2023-09-04 21:16:08 +00:00
use crate ::error ::MyError ;
2023-04-23 01:45:18 +00:00
use crate ::error ::Res ;
2023-09-04 15:36:42 +00:00
use crate ::parser ::org_source ::convert_error ;
2023-04-23 01:45:18 +00:00
use crate ::parser ::util ::blank_line ;
2023-10-02 23:24:47 +00:00
use crate ::types ::AstNode ;
2023-09-03 04:05:47 +00:00
use crate ::types ::Document ;
use crate ::types ::Object ;
2022-10-15 00:17:48 +00:00
2023-09-04 21:38:02 +00:00
/// Parse a full org-mode document.
///
2023-09-30 01:46:52 +00:00
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document without an underlying file attached.
2023-03-24 00:12:42 +00:00
#[ allow(dead_code) ]
2023-09-12 19:52:01 +00:00
pub fn parse < ' s > ( input : & ' s str ) -> Result < Document < ' s > , Box < dyn std ::error ::Error > > {
2023-09-30 01:46:52 +00:00
parse_file_with_settings ::< & Path > ( input , & GlobalSettings ::default ( ) , None )
}
/// Parse a full org-mode document.
///
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path.
///
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
#[ allow(dead_code) ]
pub fn parse_file < ' s , P : AsRef < Path > > (
input : & ' s str ,
file_path : Option < P > ,
) -> Result < Document < ' s > , Box < dyn std ::error ::Error > > {
parse_file_with_settings ( input , & GlobalSettings ::default ( ) , file_path )
2023-09-03 16:45:12 +00:00
}
2023-09-04 21:38:02 +00:00
/// Parse a full org-mode document with starting settings.
///
2023-09-30 01:46:52 +00:00
/// This is a secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied without an underlying file attached.
2023-09-04 21:38:02 +00:00
///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[ allow(dead_code) ]
2023-09-11 17:13:28 +00:00
pub fn parse_with_settings < ' g , ' s > (
2023-09-04 21:38:02 +00:00
input : & ' s str ,
global_settings : & ' g GlobalSettings < ' g , ' s > ,
2023-09-30 01:46:52 +00:00
) -> Result < Document < ' s > , Box < dyn std ::error ::Error > > {
parse_file_with_settings ::< & Path > ( input , global_settings , None )
}
/// Parse a full org-mode document with starting settings.
///
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path starting with the settings you supplied.
///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
///
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
#[ allow(dead_code) ]
pub fn parse_file_with_settings < ' g , ' s , P : AsRef < Path > > (
input : & ' s str ,
global_settings : & ' g GlobalSettings < ' g , ' s > ,
file_path : Option < P > ,
2023-09-12 19:52:01 +00:00
) -> Result < Document < ' s > , Box < dyn std ::error ::Error > > {
2023-09-04 21:38:02 +00:00
let initial_context = ContextElement ::document_context ( ) ;
let initial_context = Context ::new ( global_settings , List ::new ( & initial_context ) ) ;
let wrapped_input = OrgSource ::new ( input ) ;
2023-09-30 01:46:52 +00:00
let mut doc =
2023-09-04 21:38:02 +00:00
all_consuming ( parser_with_context! ( document_org_source ) ( & initial_context ) ) ( wrapped_input )
. map_err ( | err | err . to_string ( ) )
2023-09-30 01:46:52 +00:00
. map ( | ( _remaining , parsed_document ) | parsed_document ) ? ;
if let Some ( file_path ) = file_path {
let full_path = file_path . as_ref ( ) . canonicalize ( ) ? ;
if doc . category . is_none ( ) {
let category = full_path
2023-09-30 05:26:24 +00:00
. file_stem ( )
2023-09-30 01:46:52 +00:00
. expect ( " File should have a name. " )
. to_str ( )
. expect ( " File name should be valid utf-8. " ) ;
doc . category = Some ( category . to_owned ( ) ) ;
}
doc . path = Some ( full_path ) ;
}
Ok ( doc )
2023-09-04 21:38:02 +00:00
}
/// Parse a full org-mode document.
///
/// Use this entry point when you want to have direct control over the starting context or if you want to use this integrated with other nom parsers. For general-purpose usage, the `parse` and `parse_with_settings` functions are a lot simpler.
2023-09-04 23:17:23 +00:00
///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
2023-09-03 16:45:12 +00:00
#[ allow(dead_code) ]
2023-09-11 17:13:28 +00:00
fn document < ' b , ' g , ' r , ' s > (
2023-09-04 15:36:42 +00:00
context : RefContext < ' b , ' g , ' r , ' s > ,
input : & ' s str ,
) -> Res < & ' s str , Document < ' s > > {
let ( remaining , doc ) = document_org_source ( context , input . into ( ) ) . map_err ( convert_error ) ? ;
Ok ( ( Into ::< & str > ::into ( remaining ) , doc ) )
}
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
#[ allow(dead_code) ]
fn document_org_source < ' b , ' g , ' r , ' s > (
2023-09-03 19:44:18 +00:00
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-09-03 16:45:12 +00:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , Document < ' s > > {
2023-09-05 02:05:43 +00:00
let mut final_settings = Vec ::new ( ) ;
2023-09-04 23:17:23 +00:00
let ( _ , document_settings ) = scan_for_in_buffer_settings ( input ) ? ;
2023-09-05 02:05:43 +00:00
let setup_files : Vec < String > = document_settings
. iter ( )
. filter ( | kw | kw . key . eq_ignore_ascii_case ( " setupfile " ) )
. map ( | kw | kw . value )
. map ( | setup_file | {
context
. get_global_settings ( )
. file_access
. read_file ( setup_file )
. map_err ( | err | nom ::Err ::< CustomError < OrgSource < '_ > > > ::Failure ( err . into ( ) ) )
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
for setup_file in setup_files . iter ( ) . map ( String ::as_str ) {
let ( _ , setup_file_settings ) =
2023-10-07 02:08:26 +00:00
scan_for_in_buffer_settings ( setup_file . into ( ) ) . map_err ( | err | {
eprintln! ( " {} " , err ) ;
2023-09-05 02:05:43 +00:00
nom ::Err ::Error ( CustomError ::MyError ( MyError (
" TODO: make this take an owned string so I can dump err.to_string() into it. "
. into ( ) ,
) ) )
} ) ? ;
final_settings . extend ( setup_file_settings ) ;
2023-09-04 16:48:59 +00:00
}
2023-09-05 02:05:43 +00:00
final_settings . extend ( document_settings ) ;
2023-09-06 15:45:35 +00:00
let new_settings = apply_in_buffer_settings ( final_settings , context . get_global_settings ( ) )
2023-10-07 02:08:26 +00:00
. map_err ( | err | {
eprintln! ( " {} " , err ) ;
2023-09-06 15:45:35 +00:00
nom ::Err ::Error ( CustomError ::MyError ( MyError (
" TODO: make this take an owned string so I can dump err.to_string() into it. "
. into ( ) ,
) ) )
} ) ? ;
2023-09-06 15:00:19 +00:00
let new_context = context . with_global_settings ( & new_settings ) ;
let context = & new_context ;
2023-09-30 18:35:22 +00:00
let ( remaining , mut document ) =
2023-09-03 16:45:12 +00:00
_document ( context , input ) . map ( | ( rem , out ) | ( Into ::< & str > ::into ( rem ) , out ) ) ? ;
2023-07-14 23:06:58 +00:00
{
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
2023-09-27 23:36:23 +00:00
let all_radio_targets : Vec < & Vec < Object < '_ > > > = Into ::< AstNode > ::into ( & document )
. into_iter ( )
. filter_map ( | ast_node | {
if let AstNode ::RadioTarget ( ast_node ) = ast_node {
Some ( ast_node )
} else {
None
}
2023-07-14 23:06:58 +00:00
} )
. map ( | rt | & rt . children )
. collect ( ) ;
if ! all_radio_targets . is_empty ( ) {
2023-09-04 16:28:33 +00:00
let mut new_global_settings = context . get_global_settings ( ) . clone ( ) ;
new_global_settings . radio_targets = all_radio_targets ;
let parser_context = context . with_global_settings ( & new_global_settings ) ;
2023-10-02 14:03:04 +00:00
let ( remaining , mut document ) = _document ( & parser_context , input )
2023-09-03 16:45:12 +00:00
. map ( | ( rem , out ) | ( Into ::< & str > ::into ( rem ) , out ) ) ? ;
2023-10-02 14:03:04 +00:00
apply_post_parse_in_buffer_settings ( & mut document )
. map_err ( | err | nom ::Err ::< CustomError < OrgSource < '_ > > > ::Failure ( err . into ( ) ) ) ? ;
2023-08-23 04:30:26 +00:00
return Ok ( ( remaining . into ( ) , document ) ) ;
2023-07-14 23:06:58 +00:00
}
}
2023-09-30 18:35:22 +00:00
// Find final in-buffer settings that do not impact parsing
2023-10-02 14:03:04 +00:00
apply_post_parse_in_buffer_settings ( & mut document )
. map_err ( | err | nom ::Err ::< CustomError < OrgSource < '_ > > > ::Failure ( err . into ( ) ) ) ? ;
2023-09-30 18:35:22 +00:00
2023-08-23 04:30:26 +00:00
Ok ( ( remaining . into ( ) , document ) )
2023-07-14 23:06:58 +00:00
}
2023-08-11 00:04:59 +00:00
#[ cfg_attr(feature = " tracing " , tracing::instrument(ret, level = " debug " )) ]
2023-09-03 19:44:18 +00:00
fn _document < ' b , ' g , ' r , ' s > (
context : RefContext < ' b , ' g , ' r , ' s > ,
2023-08-23 04:30:26 +00:00
input : OrgSource < ' s > ,
) -> Res < OrgSource < ' s > , Document < ' s > > {
2023-07-14 23:06:58 +00:00
let zeroth_section_matcher = parser_with_context! ( zeroth_section ) ( context ) ;
2023-08-29 17:48:10 +00:00
let heading_matcher = parser_with_context! ( heading ( 0 ) ) ( context ) ;
2023-04-19 22:37:39 +00:00
let ( remaining , _blank_lines ) = many0 ( blank_line ) ( input ) ? ;
let ( remaining , zeroth_section ) = opt ( zeroth_section_matcher ) ( remaining ) ? ;
2023-03-27 22:08:17 +00:00
let ( remaining , children ) = many0 ( heading_matcher ) ( remaining ) ? ;
2023-03-24 21:34:56 +00:00
let source = get_consumed ( input , remaining ) ;
Ok ( (
remaining ,
Document {
2023-08-23 04:30:26 +00:00
source : source . into ( ) ,
2023-09-30 18:35:22 +00:00
category : None ,
2023-09-30 01:14:55 +00:00
path : None ,
2023-03-24 21:34:56 +00:00
zeroth_section ,
children ,
} ,
) )
2023-03-23 21:59:39 +00:00
}