72 Commits

Author SHA1 Message Date
Tom Alexander
627c785e24 Merge branch 'latex_environment_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-06 12:02:29 -04:00
Tom Alexander
758e224e6d Move consuming trailing element whitespace inside the parsers.
This ensures the parsers can take into account the affiliated keywords when setting their source without needing the SetSource trait.
2023-10-06 12:02:14 -04:00
Tom Alexander
f79606047e Compare value.
Some checks failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-05 20:13:10 -04:00
Tom Alexander
dd3de67a8c Merge branch 'babel_call_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-05 20:07:30 -04:00
Tom Alexander
823426a4f1 Cleanup. 2023-10-05 20:04:52 -04:00
Tom Alexander
fa97124186 Handle nesting of brackets.
All checks were successful
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-05 19:51:13 -04:00
Tom Alexander
885fefd060 Implement generic function for balanced brackets text. 2023-10-05 19:51:13 -04:00
Tom Alexander
efac73798f Add a test showing we need to count brackets. 2023-10-05 18:44:57 -04:00
Tom Alexander
68e392811e Parse the babel call. 2023-10-05 18:44:56 -04:00
Tom Alexander
343af41f78 Separate babel call out to its own parser. 2023-10-05 16:27:36 -04:00
Tom Alexander
f49a1853ad Merge branch 'fixed_width_area_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-05 04:14:35 -04:00
Tom Alexander
6bd8d9efd7 Cleanup. 2023-10-05 04:04:18 -04:00
Tom Alexander
18ad80e018 Compare value. 2023-10-05 03:58:42 -04:00
Tom Alexander
34a0858473 Merge branch 'diary_sexp' 2023-10-05 03:58:19 -04:00
Tom Alexander
4ba9d7439a Compare value. 2023-10-05 03:46:14 -04:00
Tom Alexander
6f0439bb6d Merge branch 'clock_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-05 03:41:02 -04:00
Tom Alexander
b478b6f5d7 Compare value, duration, and status. 2023-10-05 03:40:29 -04:00
Tom Alexander
02af3d0081 Merge branch 'lesser_block_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-05 02:37:58 -04:00
Tom Alexander
40685f05cc Add a test for multiple names on a single element. 2023-10-05 02:28:24 -04:00
Tom Alexander
e21701b97c Cleanup. 2023-10-05 02:26:11 -04:00
Tom Alexander
ef8a6884fe Remove outdated TODO. 2023-10-05 02:20:25 -04:00
Tom Alexander
ac7125d9b6 Only allow a single export type for export blocks. 2023-10-05 02:17:53 -04:00
Tom Alexander
58ca9569a6 Compare export type. 2023-10-05 02:15:32 -04:00
Tom Alexander
1da521b08a Compare value for export block. 2023-10-05 02:06:26 -04:00
Tom Alexander
386ad5091d End switches at the first unrecognized word in src blocks. 2023-10-05 01:55:33 -04:00
Tom Alexander
5f84cd974d Add test showing the first unrecognized word is the end of the switches for src blocks. 2023-10-05 01:10:50 -04:00
Tom Alexander
d8ea450a46 Add TODOs for comparing caption to elems that can have affiliated keywords. 2023-10-05 01:06:30 -04:00
Tom Alexander
3742f4fa08 Remove double option. 2023-10-05 00:53:11 -04:00
Tom Alexander
11a7234900 Handle empty parameters with whitespace preceding. 2023-10-05 00:49:15 -04:00
Tom Alexander
50a3631b79 Handle whitespace after parameters in src blocks. 2023-10-05 00:43:03 -04:00
Tom Alexander
da2d7535e8 Add synonyms for name. 2023-10-05 00:30:33 -04:00
Tom Alexander
1351577c5a Fix handling affiliated keywords before invalid paragraphs. 2023-10-04 23:40:38 -04:00
Tom Alexander
65eda08843 Re-work the element parser to handle affiliated keywords before elements that cannot have affiliated keywords. 2023-10-04 22:47:13 -04:00
Tom Alexander
b82d4c0eca Fix finding name keyword in affiliated keywords list. 2023-10-04 21:32:10 -04:00
Tom Alexander
93fe46e4e7 Populate the name field on elements. 2023-10-04 21:27:55 -04:00
Tom Alexander
5b308ea76f Implement a function to read the name from the affiliated keywords. 2023-10-04 21:12:06 -04:00
Tom Alexander
ab4a0c1224 Clean up. 2023-10-04 21:05:20 -04:00
Tom Alexander
786521ad4a Add affiliated keyword matching to the detect_* functions. 2023-10-04 21:03:32 -04:00
Tom Alexander
d8102b7bc2 Move the affiliated keywords parser inside the specific element parsers.
We need access to the affiliated keywords to do things like set the name of the element, and only half the element parsers are allowed to have affiliated keywords, so it makes sense to move it inside the specific parsers.
2023-10-04 20:55:48 -04:00
Tom Alexander
a26640355c Add check for name on paragraph. 2023-10-04 19:58:09 -04:00
Tom Alexander
057c8a1387 Compare name. 2023-10-04 19:43:34 -04:00
Tom Alexander
4fc81e983a Add tests for names for lesser blocks. 2023-10-04 19:39:50 -04:00
Tom Alexander
258e9485de Add tests for names and references in src and example blocks. 2023-10-04 19:34:10 -04:00
Tom Alexander
87ac18e6b2 Add real handling for preserve indent.
Now that I know which flag changes this setting, we can handle it properly.
2023-10-04 19:23:47 -04:00
Tom Alexander
e1e4ac75e4 Add a test for preserve indent. 2023-10-04 19:05:28 -04:00
Tom Alexander
c877116540 Fix handling of spaces between language, switches, and parameters. 2023-10-04 18:57:51 -04:00
Tom Alexander
8e70773b15 Fix handling cases where only language is specified before parameters. 2023-10-04 18:11:38 -04:00
Tom Alexander
f046b16c11 Compare src block parameters. 2023-10-04 18:02:50 -04:00
Tom Alexander
1ab7d2f2d7 Add a test showing we are not handling exports flags properly. 2023-10-04 17:32:51 -04:00
Tom Alexander
a548c7e170 Exclude language from the switches property. 2023-10-04 17:11:13 -04:00
Tom Alexander
b556f4617f Add src block properties.
These are largely the same as example blocks but with a :language property.
2023-10-04 16:58:45 -04:00
Tom Alexander
13163f2468 Retain labels stays a boolean without -r. 2023-10-04 16:37:15 -04:00
Tom Alexander
da5dcd4c1b Support multiple commas when escaping lines. 2023-10-04 16:03:45 -04:00
Tom Alexander
d059afef07 Add a setting for coderef_label_format. 2023-10-04 15:48:57 -04:00
Tom Alexander
bcade66e68 Retain labels is actually either a boolean or a number. 2023-10-04 15:43:09 -04:00
Tom Alexander
301a6db83e Fix retain labels.
This is a numeric value based on the character offset of -k from the beginning of the switches.
2023-10-04 15:21:27 -04:00
Tom Alexander
32da06776c Handle matching no switches. 2023-10-04 15:21:27 -04:00
Tom Alexander
169bf69f5e Preserve the leading whitespace before an escape. 2023-10-04 15:21:27 -04:00
Tom Alexander
7ee48ff65c Switch to handling the unescaping during the initial parsing.
This preserves the line ending characters unlike the rust .lines() iterator.
2023-10-04 15:21:27 -04:00
Tom Alexander
afb43ff34f Switch to getting the contents with a function to handle the escaped lines. 2023-10-04 15:21:27 -04:00
Tom Alexander
b56d847cfa Compare label format, retain labels, and use labels. 2023-10-04 15:21:27 -04:00
Tom Alexander
1503054994 Make an argument for the line number switch optional. 2023-10-04 11:46:02 -04:00
Tom Alexander
03028889bd Fix capturing trailing whitespace for switches. 2023-10-04 11:34:01 -04:00
Tom Alexander
317293f0f2 Extract the line number from the switches. 2023-10-04 11:31:45 -04:00
Tom Alexander
3d7f411cf9 Compare number lines for example blocks. 2023-10-04 10:31:01 -04:00
Tom Alexander
650cbc17db Compare switches for example block. 2023-10-04 10:04:11 -04:00
Tom Alexander
1d7770e590 Rename data to switches in example and src block. 2023-10-04 09:59:11 -04:00
Tom Alexander
bf038db31c Add test showing trailing whitespace is captured in the switches. 2023-10-04 09:53:33 -04:00
Tom Alexander
4cdf88a632 Switches are not stored for comment blocks, but they are allowed to appear. 2023-10-04 09:51:28 -04:00
Tom Alexander
2eaef82fdb Organize lesser block tests into subfolders. 2023-10-04 09:46:09 -04:00
Tom Alexander
00dc7b636c Add more tests. 2023-10-04 09:42:36 -04:00
Tom Alexander
e6c809ab03 Compare value for comment block. 2023-10-04 09:35:19 -04:00
95 changed files with 2250 additions and 318 deletions

View File

@@ -46,6 +46,13 @@ dockertest:
> $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: buildtest
buildtest:
> cargo build --no-default-features
> cargo build --no-default-features --features compare
> cargo build --no-default-features --features tracing
> cargo build --no-default-features --features compare,tracing
.PHONY: foreign_document_test
foreign_document_test:
> $(MAKE) -C docker/organic_test run_foreign_document_test

View File

@@ -0,0 +1,17 @@
#+name: foo
: bar
#+source: foo
: bar
#+tblname: foo
: bar
#+resname: foo
: bar
#+srcname: foo
: bar
#+label: foo
: bar

View File

@@ -0,0 +1,8 @@
#+NAME: foo
bar
#+NaMe: baz
cat
#+name: lorem
ipsum

View File

@@ -0,0 +1,6 @@
#+name: foo
#+source: bar
#+name: baz
#+tblname: lorem
#+label: ipsum
: dolar

View File

@@ -0,0 +1,2 @@
#+tblname: foo
: bar

View File

@@ -0,0 +1,10 @@
* Headline
before
#+NAME: foo
:candle:
inside
the drawer
:end:
after

View File

@@ -0,0 +1,7 @@
#+NAME: foo
#+BEGIN: clocktable :scope file :maxlevel 2
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
| Headline | Time |
|--------------+--------|
| *Total time* | *0:00* |
#+END:

View File

@@ -0,0 +1,8 @@
#+begin_center
#+end_center
#+begin_center
#+NAME: foo
#+end_center

View File

@@ -0,0 +1,4 @@
#+NAME: foo
#+begin_center
#+end_center

View File

@@ -0,0 +1,4 @@
#+NAME: foo
#+begin_quote
#+end_quote

View File

@@ -0,0 +1,6 @@
#+NAME: foo
#+begin_defun
foo
{{{bar(baz)}}}
#+end_defun

View File

@@ -0,0 +1,2 @@
#+NAME: foo
1. bar

View File

@@ -0,0 +1,2 @@
#+NAME: foo
| foo | bar |

View File

@@ -0,0 +1,3 @@
#+call:
#+call:

View File

@@ -0,0 +1,3 @@
#+call: foo[inside](bar="baz")[outside]
#+call: foo[](bar="baz")[]

View File

@@ -0,0 +1,7 @@
#+call: foo[inside](bar="baz")[outside]
#+call: foo[[inside]](bar="baz")[outside]
#+call: foo[inside]((bar="baz"))[outside]
#+call: foo[inside](bar="baz")[[outside]]

View File

@@ -0,0 +1 @@
#+call: foo(bar="baz"

View File

@@ -0,0 +1,7 @@
#+call: foo(bar="baz")
#+call: lorem ipsum
#+call: dolar cat(dog)
#+call: (bat)

View File

@@ -0,0 +1,3 @@
#+call: foo [inside] (bar="baz") [outside]
#+call: foo (bar="baz") [outside]

View File

@@ -0,0 +1 @@
CLOCK: [2023-04-21 Fri 19:32]--[2023-04-21 Fri 19:35]

View File

@@ -0,0 +1,3 @@
CLOCK: [2023-04-21 Fri 19:32]--[2023-04-21 Fri 19:35] => 0:03
#+NAME: foo
CLOCK: [2023-04-21 Fri 19:43]

View File

@@ -0,0 +1 @@
CLOCK: [2023-04-21 Fri 19:43] => 0:03

View File

@@ -0,0 +1,2 @@
CLOCK: [1970-01-01 Thu 8:15-13:15otherrest +1w -1d] => 0:03
CLOCK: [1970-01-01 Thu 8:15-13:15otherrest +1w -1d]

View File

@@ -0,0 +1,2 @@
#+NAME: foo
# Comments cannot have affiliated keywords.

View File

@@ -0,0 +1,2 @@
#+NAME: foo
%%(foo)

View File

@@ -0,0 +1,2 @@
#+NAME: foo
: bar

View File

@@ -0,0 +1,2 @@
#+NAME: foo
-----

View File

@@ -0,0 +1,2 @@
#+NAME: foo
#+FOO: BAR

View File

@@ -0,0 +1,4 @@
#+NAME: foo
\begin{foo}
bar
\end{foo}

View File

@@ -0,0 +1,10 @@
#+begin_comment
,* foo
,,,** bar
,*** baz
lorem
, ipsum
,#+begin_src dolar
,#+end_src
#+end_comment

View File

@@ -0,0 +1,4 @@
#+NAME: foo
#+begin_comment text
bar
#+end_comment

View File

@@ -0,0 +1,4 @@
#+begin_comment
This is a comment
,* with an escaped line.
#+end_comment

View File

@@ -0,0 +1,3 @@
#+begin_comment -n 20
foo
#+end_comment

View File

@@ -0,0 +1,10 @@
#+begin_example
,* foo
,,,** bar
,*** baz
lorem
, ipsum
,#+begin_src dolar
,#+end_src
#+end_example

View File

@@ -0,0 +1,7 @@
#+begin_example python :exports results
print("foo")
#+end_example
#+begin_example python -n :exports results
print("foo")
#+end_example

View File

@@ -0,0 +1,3 @@
#+begin_example elisp -n 5
foo
#+end_example

View File

@@ -0,0 +1,7 @@
#+begin_example -n -10
foo
#+end_example
#+begin_example +n -15
bar
#+end_example

View File

@@ -0,0 +1,7 @@
#+begin_example -n 0
foo
#+end_example
#+begin_example +n 0
bar
#+end_example

View File

@@ -0,0 +1,8 @@
#+begin_example -n 5
foo
#+end_example
# Line numbering starts at 15 for the example below since it uses +n.
#+begin_example +n 10
bar
#+end_example

View File

@@ -0,0 +1,4 @@
#+NAME: foo
#+begin_example text
bar
#+end_example

View File

@@ -0,0 +1,3 @@
#+begin_example foo -k
bar
#+end_example

View File

@@ -0,0 +1,3 @@
#+begin_example foo -n bar -k baz
#+end_example

View File

@@ -0,0 +1,15 @@
#+begin_example text -i
foo
#+end_example
#+begin_example text -n -i
foo
#+end_example
#+begin_example text
foo
#+end_example
#+begin_example text -n -r -k
foo
#+end_example

View File

@@ -0,0 +1,6 @@
#+begin_example text
foo
bar (ref:here)
baz
#+end_example
Link to the reference: [[(here)]]

View File

@@ -0,0 +1,2 @@
#+begin_example
#+end_example

View File

@@ -0,0 +1,15 @@
#+BEGIN_EXAMPLE elisp -n -r -l "((%s))"
foo
#+END_EXAMPLE
#+BEGIN_EXAMPLE elisp -k -n -r -l "((%s))"
foo
#+END_EXAMPLE
#+BEGIN_EXAMPLE elisp -k 8 -n -r -l "((%s))"
foo
#+END_EXAMPLE
#+BEGIN_EXAMPLE elisp -n -r -k -l "((%s))"
foo
#+END_EXAMPLE

View File

@@ -0,0 +1,3 @@
#+begin_example +n 10
foo
#+end_example

View File

@@ -0,0 +1,10 @@
#+begin_export html
,* foo
,,,** bar
,*** baz
lorem
, ipsum
,#+begin_src dolar
,#+end_src
#+end_export

View File

@@ -0,0 +1,4 @@
#+NAME: foo
#+begin_export text
bar
#+end_export

View File

@@ -0,0 +1,3 @@
#+begin_export latex
This would be LaTeX code.
#+end_export

View File

@@ -0,0 +1,6 @@
# Verse blocks are the only lesser blocks that contain objects
#+begin_verse
#+begin_comment
This is a comment.
#+end_comment
#+end_verse

View File

@@ -0,0 +1,7 @@
#+begin_src python :exports results
print("foo")
#+end_src
#+begin_src python -n :exports results
print("foo")
#+end_src

View File

@@ -0,0 +1,4 @@
#+NAME: foo
#+begin_src text
bar
#+end_src

View File

@@ -0,0 +1,7 @@
#+begin_src python :exports results
print("foo")
#+end_src
#+begin_src python -n :exports results
print("foo")
#+end_src

View File

@@ -0,0 +1,3 @@
#+begin_src foo -n bar -k baz
#+end_src

View File

@@ -0,0 +1,15 @@
#+begin_src text -i
foo
#+end_src
#+begin_src text -n -i
foo
#+end_src
#+begin_src text
foo
#+end_src
#+begin_src text -n -r -k
foo
#+end_src

View File

@@ -0,0 +1,6 @@
#+begin_src text
foo
bar (ref:here)
baz
#+end_src
Link to the reference: [[(here)]]

View File

@@ -0,0 +1,4 @@
#+NAME: foo
#+begin_verse text
bar
#+end_verse

View File

@@ -0,0 +1,2 @@
#+NAME: foo
bar

View File

@@ -1,6 +1,7 @@
use std::path::Path;
use crate::compare::diff::compare_document;
use crate::compare::diff::DiffResult;
use crate::compare::parse::emacs_parse_anonymous_org_document;
use crate::compare::parse::emacs_parse_file_org_document;
use crate::compare::parse::get_emacs_version;
@@ -43,6 +44,12 @@ pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
if diff_result.is_bad() {
Err("Diff results do not match.")?;
} else {
println!(
"{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0),
reset = DiffResult::reset_color(),
);
}
Ok(())
@@ -83,6 +90,12 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
if diff_result.is_bad() {
Err("Diff results do not match.")?;
} else {
println!(
"{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0),
reset = DiffResult::reset_color(),
);
}
Ok(())

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::OrgSource;
use crate::types::Keyword;
#[derive(Debug)]
pub(crate) enum ContextElement<'r, 's> {
@@ -27,11 +28,22 @@ pub(crate) enum ContextElement<'r, 's> {
/// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool),
/// Indicate that we are parsing a paragraph that already has affiliated keywords.
///
/// The value stored is the start of the element after the affiliated keywords. In this way, we can ensure that we do not exit an element immediately after the affiliated keyword had been consumed.
HasAffiliatedKeyword(HasAffiliatedKeywordInner<'r, 's>),
/// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement.
#[allow(dead_code)]
Placeholder(PhantomData<&'s str>),
}
#[derive(Debug, Clone)]
pub(crate) struct HasAffiliatedKeywordInner<'r, 's> {
pub(crate) start_after_affiliated_keywords: OrgSource<'s>,
pub(crate) keywords: &'r Vec<Keyword<'s>>,
}
pub(crate) struct ExitMatcherNode<'r> {
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
pub(crate) exit_matcher: &'r DynContextMatcher<'r>,

View File

@@ -32,6 +32,11 @@ pub struct GlobalSettings<'g, 's> {
///
/// Corresponds to org-footnote-section elisp variable.
pub footnote_section: &'g str,
/// The label format for references inside src/example blocks.
///
/// Corresponds to org-coderef-label-format elisp variable.
pub coderef_label_format: &'g str,
}
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@@ -49,6 +54,7 @@ impl<'g, 's> GlobalSettings<'g, 's> {
tab_width: DEFAULT_TAB_WIDTH,
odd_levels_only: HeadlineLevelFilter::default(),
footnote_section: "Footnotes",
coderef_label_format: "(ref:%s)",
}
}
}

View File

@@ -21,6 +21,7 @@ type DynMatcher<'c> = dyn Matcher + 'c;
pub(crate) use context::Context;
pub(crate) use context::ContextElement;
pub(crate) use context::ExitMatcherNode;
pub(crate) use context::HasAffiliatedKeywordInner;
pub(crate) use exiting::ExitClass;
pub use file_access_interface::FileAccessInterface;
pub use file_access_interface::LocalFileAccessInterface;

240
src/parser/babel_call.rs Normal file
View File

@@ -0,0 +1,240 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use nom::InputTake;
use super::keyword::affiliated_keyword;
use super::org_source::BracketDepth;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::start_of_line;
use super::OrgSource;
use crate::context::Matcher;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::org_line_ending;
use crate::types::BabelCall;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn babel_call<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCall<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
BabelCall {
source: Into::<&str>::into(source),
name: get_name(&affiliated_keywords),
value: Into::<&str>::into(line_break.take(0)),
call: None,
inside_header: None,
arguments: None,
end_header: None,
},
));
}
let (remaining, _ws) = space0(remaining)?;
let (remaining, (value, (call, inside_header, arguments, end_header))) =
consumed(babel_call_value)(remaining)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
BabelCall {
source: Into::<&str>::into(source),
name: get_name(&affiliated_keywords),
value: Into::<&str>::into(value).trim_end(),
call: call.map(Into::<&str>::into),
inside_header: inside_header.map(Into::<&str>::into),
arguments: arguments.map(Into::<&str>::into),
end_header: end_header.map(Into::<&str>::into),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value<'s>(
input: OrgSource<'s>,
) -> Res<
OrgSource<'s>,
(
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
Option<OrgSource<'s>>,
),
> {
let (remaining, call) = opt(babel_call_call)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
Ok((
remaining,
(call, inside_header, arguments.flatten(), end_header),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(
recognize(many_till(
anychar,
alt((
peek(recognize(one_of("[("))),
recognize(tuple((space0, org_line_ending))),
)),
)),
|s| s.len() > 0,
)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inside_header<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, contents) = balanced_bracket(
|i| tag("[")(i),
|i| peek(tag("]"))(i),
|i| recognize(tuple((space0, org_line_ending)))(i),
|i| tag("]")(i),
|s| s.get_bracket_depth(),
)(input)?;
let (contents_start, _) = tag("[")(input)?;
Ok((remaining, contents.unwrap_or(contents_start.take(0))))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn arguments<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
balanced_bracket(
|i| tag("(")(i),
|i| peek(tag(")"))(i),
|i| recognize(tuple((space0, org_line_ending)))(i),
|i| tag(")")(i),
|s| s.get_parenthesis_depth(),
)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn end_header<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = space0(input)?;
verify(
recognize(many_till(anychar, peek(tuple((space0, org_line_ending))))),
|s| s.len() > 0,
)(remaining)
}
fn balanced_bracket<
O: Matcher,
S: Matcher,
F: Matcher,
E: Matcher,
D: for<'ss> Fn(OrgSource<'ss>) -> BracketDepth,
>(
opening_parser: O,
stop_parser: S,
fail_parser: F,
end_parser: E,
depth_function: D,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
move |input| {
impl_balanced_bracket::<&O, &S, &F, &E, &D>(
input,
&opening_parser,
&stop_parser,
&fail_parser,
&end_parser,
&depth_function,
)
}
}
fn impl_balanced_bracket<
's,
O: Matcher,
S: Matcher,
F: Matcher,
E: Matcher,
D: for<'ss> Fn(OrgSource<'ss>) -> BracketDepth,
>(
input: OrgSource<'s>,
opening_parser: O,
stop_parser: S,
fail_parser: F,
end_parser: E,
depth_function: D,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
let (mut remaining, _) = opening_parser(input)?;
let contents_start = remaining;
let original_depth = depth_function(remaining);
loop {
let bracket_depth = depth_function(remaining);
if bracket_depth == original_depth {
let (remain, stop_result) = opt(&stop_parser)(remaining)?;
remaining = remain;
if stop_result.is_some() {
break;
}
}
if fail_parser(remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Fail parser matched.",
))));
}
let (remain, _) = anychar(remaining)?;
remaining = remain;
}
let contents_end = remaining;
let (remaining, _) = end_parser(remaining)?;
let contents = if contents_start != contents_end {
Some(contents_start.get_until(contents_end))
} else {
None
};
Ok((remaining, contents))
}
#[cfg(test)]
mod tests {
use nom::combinator::opt;
use super::*;
#[test]
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("()");
let (remaining, call) = opt(babel_call_call)(input)?;
assert_eq!(Into::<&str>::into(remaining), "()");
assert_eq!(call, None);
Ok(())
}
}

View File

@@ -1,23 +1,28 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::digit1;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::timestamp::inactive_date_range_timestamp;
use super::timestamp::inactive_time_range_timestamp;
use super::timestamp::inactive_timestamp;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::Clock;
use crate::types::ClockStatus;
use crate::types::Timestamp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn clock<'b, 'g, 'r, 's>(
@@ -29,52 +34,56 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
let (remaining, _clock) = tag_no_case("clock:")(remaining)?;
let (remaining, _gap_whitespace) = space1(remaining)?;
let (remaining, _timestamp_junk) = alt((
parser_with_context!(inactive_timestamp_range_duration)(context),
parser_with_context!(inactive_timestamp)(context),
))(remaining)?;
let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?;
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Clock {
source: source.into(),
timestamp,
duration,
status: if duration.is_some() {
ClockStatus::Closed
} else {
ClockStatus::Running
},
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp_range_duration<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn clock_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
tag("["),
is_not("\r\n]"),
tag("]--["),
is_not("\r\n]"),
tag("]"),
space1,
tag("=>"),
space1,
digit1,
tag(":"),
verify(digit1, |mm: &OrgSource<'_>| mm.len() == 2),
space0,
alt((line_ending, eof)),
)))(input)
) -> Res<OrgSource<'s>, (Timestamp<'s>, Option<&'s str>)> {
// TODO: This would be more efficient if we didn't throw away the parse result of the first half of an active/inactive date range timestamp if the parse fails (as in, the first thing active_date_range_timestamp parses is a active_timestamp but then we throw that away if it doesn't turn out to be a full active_date_range_timestamp despite the active_timestamp parse being completely valid). I am going with the simplest/cleanest approach for the first implementation.
alt((
// Order matters here. If its a date range, we need to parse the entire date range instead of just the first timestamp. If its a time range, we need to make sure thats parsed as a time range instead of as the "rest" portion of a single timestamp.
map(
parser_with_context!(inactive_time_range_timestamp)(context),
|timestamp| (timestamp, None),
),
map(
tuple((
parser_with_context!(inactive_date_range_timestamp)(context),
opt(duration),
)),
|(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)),
),
map(
parser_with_context!(inactive_timestamp)(context),
|timestamp| (timestamp, None),
),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
tag("["),
is_not("\r\n]"),
tag("]"),
space0,
alt((line_ending, eof)),
)))(input)
fn duration<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tuple((tag("=>"), space1))(input)?;
let (remaining, duration) = recognize(tuple((digit1, tag(":"), digit1)))(remaining)?;
Ok((remaining, duration))
}

View File

@@ -13,6 +13,7 @@ use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
@@ -43,6 +44,8 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
let last_line = remaining_lines.pop();

View File

@@ -1,11 +1,14 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::get_consumed;
@@ -14,25 +17,30 @@ use crate::types::DiarySexp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
start_of_line(input)?;
let (remaining, _clock) = tag("%%(")(input)?;
let (remaining, _contents) = is_not("\r\n")(remaining)?;
let (remaining, _eol) = alt((line_ending, eof))(remaining)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
let (remaining, _eol) = org_line_ending(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
DiarySexp {
source: source.into(),
name: get_name(&affiliated_keywords),
value: Into::<&str>::into(value),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, tag("%%(")))(input)?;
Ok((input, ()))
}

View File

@@ -7,10 +7,14 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -41,8 +45,9 @@ pub(crate) fn drawer<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(),
))));
}
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
tag(":"),
name,
@@ -84,13 +89,16 @@ pub(crate) fn drawer<'b, 'g, 'r, 's>(
};
let (remaining, _end) = drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Drawer {
source: source.into(),
name: drawer_name.into(),
name: get_name(&affiliated_keywords),
drawer_name: drawer_name.into(),
children,
},
))

View File

@@ -17,7 +17,10 @@ use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -47,8 +50,10 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(),
))));
}
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;
let (remaining, (_, name, parameters, _, _)) = tuple((
recognize(tuple((tag_no_case("#+begin:"), space1))),
name,
@@ -92,12 +97,15 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
DynamicBlock {
source: source.into(),
name: name.into(),
name: get_name(&affiliated_keywords),
block_name: name.into(),
parameters: parameters.map(|val| val.into()),
children,
},

View File

@@ -1,7 +1,12 @@
use nom::branch::alt;
use nom::combinator::map;
use nom::multi::many0;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::sequence::tuple;
#[cfg(feature = "tracing")]
use tracing::span;
use super::babel_call::babel_call;
use super::clock::clock;
use super::comment::comment;
use super::comment::detect_comment;
@@ -16,7 +21,7 @@ use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::babel_call_keyword;
use super::keyword::affiliated_keyword_as_regular_keyword;
use super::keyword::keyword;
use super::latex_environment::latex_environment;
use super::lesser_block::comment_block;
@@ -29,8 +34,6 @@ use super::paragraph::paragraph;
use super::plain_list::detect_plain_list;
use super::plain_list::plain_list;
use super::table::detect_table;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
@@ -38,7 +41,6 @@ use crate::error::MyError;
use crate::error::Res;
use crate::parser::table::org_mode_table;
use crate::types::Element;
use crate::types::SetSource;
pub(crate) const fn element(
can_be_paragraph: bool,
@@ -72,58 +74,98 @@ fn _element<'b, 'g, 'r, 's>(
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call_keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context);
// TODO: Affiliated keywords cannot be on comments, clocks, headings, inlinetasks, items, node properties, planning, property drawers, sections, and table rows
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
let (remaining, mut element) = match alt((
map(plain_list_matcher, Element::PlainList),
greater_block_matcher,
map(dynamic_block_matcher, Element::DynamicBlock),
map(footnote_definition_matcher, Element::FootnoteDefinition),
map(comment_matcher, Element::Comment),
map(drawer_matcher, Element::Drawer),
map(table_matcher, Element::Table),
map(verse_block_matcher, Element::VerseBlock),
map(comment_block_matcher, Element::CommentBlock),
map(example_block_matcher, Element::ExampleBlock),
map(export_block_matcher, Element::ExportBlock),
map(src_block_matcher, Element::SrcBlock),
map(clock_matcher, Element::Clock),
map(diary_sexp_matcher, Element::DiarySexp),
map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword),
))(remaining)
{
the_ok @ Ok(_) => the_ok,
Err(_) => {
if can_be_paragraph {
match map(paragraph_matcher, Element::Paragraph)(remaining) {
the_ok @ Ok(_) => the_ok,
Err(_) => {
// TODO: Because this function expects a single element, if there are multiple affiliated keywords before an element that cannot have affiliated keywords, we end up re-parsing the affiliated keywords many times.
affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input)
}
}
} else {
affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input)
}
let (mut remaining, mut maybe_element) = {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Main element block");
#[cfg(feature = "tracing")]
let _enter = span.enter();
opt(alt((
map(plain_list_matcher, Element::PlainList),
greater_block_matcher,
map(dynamic_block_matcher, Element::DynamicBlock),
map(footnote_definition_matcher, Element::FootnoteDefinition),
map(comment_matcher, Element::Comment),
map(drawer_matcher, Element::Drawer),
map(table_matcher, Element::Table),
map(verse_block_matcher, Element::VerseBlock),
map(comment_block_matcher, Element::CommentBlock),
map(example_block_matcher, Element::ExampleBlock),
map(export_block_matcher, Element::ExportBlock),
map(src_block_matcher, Element::SrcBlock),
map(clock_matcher, Element::Clock),
map(diary_sexp_matcher, Element::DiarySexp),
map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword),
)))(input)?
};
if maybe_element.is_none() && can_be_paragraph {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Paragraph with affiliated keyword.");
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, paragraph_with_affiliated_keyword) = opt(map(
tuple((
peek(affiliated_keyword),
map(paragraph_matcher, Element::Paragraph),
)),
|(_, paragraph)| paragraph,
))(remaining)?;
if paragraph_with_affiliated_keyword.is_some() {
remaining = remain;
maybe_element = paragraph_with_affiliated_keyword;
}
}?;
}
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
if maybe_element.is_none() {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Affiliated keyword as regular keyword."
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
let source = get_consumed(input, remaining);
element.set_source(source.into());
let (remain, kw) = opt(map(
parser_with_context!(affiliated_keyword_as_regular_keyword)(context),
Element::Keyword,
))(remaining)?;
if kw.is_some() {
maybe_element = kw;
remaining = remain;
}
}
if maybe_element.is_none() && can_be_paragraph {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Paragraph without affiliated keyword."
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, paragraph_without_affiliated_keyword) =
map(paragraph_matcher, Element::Paragraph)(remaining)?;
remaining = remain;
maybe_element = Some(paragraph_without_affiliated_keyword);
}
if maybe_element.is_none() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element.",
))));
}
let element = maybe_element.expect("The above if-statement ensures this is Some().");
Ok((remaining, element))
}

View File

@@ -1,17 +1,18 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::only_space1;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::RefContext;
@@ -26,17 +27,35 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, _first_line) = fixed_width_area_line_matcher(input)?;
let (remaining, _remaining_lines) =
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
let last_line = remaining_lines.pop();
if let Some(last_line) = last_line {
value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
let last_line = Into::<&str>::into(last_line);
// Trim the line ending from the final line.
value.push(&last_line[..(last_line.len() - 1)])
} else {
// Trim the line ending from the only line.
let only_line = Into::<&str>::into(first_line);
value.push(&only_line[..(only_line.len() - 1)])
}
Ok((
remaining,
FixedWidthArea {
source: source.into(),
name: get_name(&affiliated_keywords),
value,
},
))
}
@@ -47,23 +66,23 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _indent) = space0(input)?;
let (remaining, _) = tuple((
tag(":"),
alt((recognize(tuple((only_space1, is_not("\r\n")))), space0)),
org_line_ending,
))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
let (remaining, _) = tuple((space0, tag(":")))(input)?;
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
return Ok((remaining, line_break));
}
let (remaining, _) = tag(" ")(remaining)?;
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
Ok((remaining, value))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((
start_of_line,
space0,
tag(":"),
alt((tag(" "), line_ending, eof)),
alt((tag(" "), org_line_ending)),
))(input)?;
Ok((input, ()))
}

View File

@@ -11,8 +11,11 @@ use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::include_input;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::WORD_CONSTITUENT_CHARACTERS;
use crate::context::parser_with_context;
use crate::context::ContextElement;
@@ -41,7 +44,8 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(),
))));
}
start_of_line(input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
// Cannot be indented.
let (remaining, (_, lbl, _, _, _)) = tuple((
tag_no_case("[fn:"),
@@ -51,7 +55,7 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
opt(verify(many0(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() <= 2
})),
))(input)?;
))(remaining)?;
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("footnote definition"),
@@ -80,11 +84,14 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
}
}
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
FootnoteDefinition {
source: source.into(),
name: get_name(&affiliated_keywords),
label: lbl.into(),
children: children.into_iter().map(|(_, item)| item).collect(),
},
@@ -119,6 +126,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}

View File

@@ -17,8 +17,11 @@ use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::in_section;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
@@ -35,6 +38,7 @@ use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::CenterBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::QuoteBlock;
use crate::types::SetSource;
@@ -45,6 +49,8 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
let pre_affiliated_keywords_input = input;
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name)) = tuple((
@@ -58,9 +64,24 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
))(remaining)?;
let name = Into::<&str>::into(name);
let (remaining, element) = match name.to_lowercase().as_str() {
"center" => center_block(context, remaining, input)?,
"quote" => quote_block(context, remaining, input)?,
_ => special_block(name)(context, remaining, input)?,
"center" => center_block(
context,
remaining,
pre_affiliated_keywords_input,
&affiliated_keywords,
)?,
"quote" => quote_block(
context,
remaining,
pre_affiliated_keywords_input,
&affiliated_keywords,
)?,
_ => special_block(name)(
context,
remaining,
pre_affiliated_keywords_input,
&affiliated_keywords,
)?,
};
Ok((remaining, element))
}
@@ -69,13 +90,23 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
fn center_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>,
affiliated_keywords: &Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, (source, children)) =
greater_block_body(context, input, original_input, "center", "center block")?;
let (remaining, (source, children)) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
"center",
"center block",
)?;
Ok((
remaining,
Element::CenterBlock(CenterBlock { source, children }),
Element::CenterBlock(CenterBlock {
source,
name: get_name(&affiliated_keywords),
children,
}),
))
}
@@ -83,13 +114,23 @@ fn center_block<'b, 'g, 'r, 's>(
fn quote_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>,
affiliated_keywords: &Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, (source, children)) =
greater_block_body(context, input, original_input, "quote", "quote block")?;
let (remaining, (source, children)) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
"quote",
"quote block",
)?;
Ok((
remaining,
Element::QuoteBlock(QuoteBlock { source, children }),
Element::QuoteBlock(QuoteBlock {
source,
name: get_name(&affiliated_keywords),
children,
}),
))
}
@@ -99,11 +140,19 @@ fn special_block<'s>(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
OrgSource<'s>,
&Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>>
+ 's {
let context_name = format!("special block {}", name);
move |context, input, original_input| {
_special_block(context, input, original_input, name, context_name.as_str())
move |context, input, pre_affiliated_keywords_input, affiliated_keywords| {
_special_block(
context,
input,
pre_affiliated_keywords_input,
name,
context_name.as_str(),
affiliated_keywords,
)
}
}
@@ -111,19 +160,26 @@ fn special_block<'s>(
fn _special_block<'c, 'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>,
name: &'s str,
context_name: &'c str,
affiliated_keywords: &Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) =
greater_block_body(context, remaining, original_input, name, context_name)?;
let (remaining, (source, children)) = greater_block_body(
context,
remaining,
pre_affiliated_keywords_input,
name,
context_name,
)?;
Ok((
remaining,
Element::SpecialBlock(SpecialBlock {
source,
name: get_name(&affiliated_keywords),
children,
name,
block_type: name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
}),
))
@@ -133,7 +189,7 @@ fn _special_block<'c, 'b, 'g, 'r, 's>(
fn greater_block_body<'c, 'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>,
name: &'c str,
context_name: &'c str,
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
@@ -178,7 +234,9 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
let source = get_consumed(original_input, remaining);
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(pre_affiliated_keywords_input, remaining);
Ok((remaining, (Into::<&str>::into(source), children)))
}

View File

@@ -5,10 +5,15 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1_count;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::start_of_line;
@@ -16,20 +21,25 @@ use crate::types::HorizontalRule;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
start_of_line(input)?;
let (remaining, rule) = recognize(tuple((
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
let (remaining, _rule) = recognize(tuple((
space0,
verify(many1_count(tag("-")), |dashes| *dashes >= 5),
space0,
alt((line_ending, eof)),
)))(input)?;
)))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
HorizontalRule {
source: rule.into(),
source: source.into(),
name: get_name(&affiliated_keywords),
},
))
}

View File

@@ -13,18 +13,21 @@ use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::Matcher;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::util::start_of_line;
use crate::types::BabelCall;
use crate::types::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
@@ -61,6 +64,7 @@ fn _filtered_keyword<'s, F: Matcher>(
remaining,
Keyword {
source: consumed_input.into(),
name: None, // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: "".into(),
},
@@ -78,6 +82,7 @@ fn _filtered_keyword<'s, F: Matcher>(
remaining,
Keyword {
source: consumed_input.into(),
name: None, // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: parsed_value.into(),
},
@@ -86,41 +91,37 @@ fn _filtered_keyword<'s, F: Matcher>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(regular_keyword_key)(input)
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
kw.name = get_name(&affiliated_keywords);
kw.source = Into::<&str>::into(source);
Ok((remaining, kw))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
pub(crate) fn affiliated_keyword_as_regular_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
let (remaining, mut kw) = affiliated_keyword(input)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
kw.source = Into::<&str>::into(source);
Ok((remaining, kw))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(affiliated_key)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCall<'s>> {
let (remaining, kw) = filtered_keyword(babel_call_key)(input)?;
Ok((
remaining,
BabelCall {
source: kw.source,
key: kw.key,
value: kw.value,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag_no_case("call")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn table_formula_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,

View File

@@ -8,11 +8,15 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
@@ -29,8 +33,10 @@ pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let value_start = remaining;
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
tag_no_case(r#"\begin{"#),
name,
@@ -48,12 +54,18 @@ pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
let (remaining, _contents) = contents(&latex_environment_end_specialized)(context, remaining)?;
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let value_end = remaining;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let value = get_consumed(value_start, value_end);
Ok((
remaining,
LatexEnvironment {
source: source.into(),
name: get_name(&affiliated_keywords),
value: value.into(),
},
))
}

View File

@@ -1,24 +1,34 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line;
@@ -26,12 +36,16 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::parser::util::text_until_exit;
use crate::types::CharOffsetInLine;
use crate::types::CommentBlock;
use crate::types::ExampleBlock;
use crate::types::ExportBlock;
use crate::types::LineNumber;
use crate::types::Object;
use crate::types::PlainText;
use crate::types::RetainLabels;
use crate::types::SrcBlock;
use crate::types::SwitchNumberLines;
use crate::types::VerseBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -39,7 +53,8 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, VerseBlock<'s>> {
let (remaining, name) = lesser_block_begin("verse")(context, input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("verse")(context, remaining)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("verse");
@@ -77,12 +92,14 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
};
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
VerseBlock {
source: source.into(),
name: name.into(),
name: get_name(&affiliated_keywords),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
children,
},
@@ -94,8 +111,9 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CommentBlock<'s>> {
let (remaining, name) = lesser_block_begin("comment")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("comment")(context, remaining)?;
let (remaining, _parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("comment");
let contexts = [
@@ -109,21 +127,18 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
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]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
CommentBlock {
source: source.into(),
name: name.into(),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
name: get_name(&affiliated_keywords),
contents: contents.into(),
},
))
@@ -134,8 +149,9 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
let (remaining, _name) = lesser_block_begin("example")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("example")(context, remaining)?;
let (remaining, parameters) = opt(tuple((space1, example_switches)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("example");
let contexts = [
@@ -149,22 +165,44 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
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]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let parameters = parameters.map(|(_, parameters)| parameters);
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
if let Some(parameters) = parameters {
(
if parameters.source.len() == 0 {
None
} else {
Some(parameters.source)
},
parameters.number_lines,
parameters.preserve_indent,
parameters.retain_labels,
parameters.use_labels,
parameters.label_format,
)
} else {
(None, None, None, RetainLabels::Yes, true, None)
}
};
Ok((
remaining,
ExampleBlock {
source: source.into(),
name: source.into(),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents: contents.into(),
name: get_name(&affiliated_keywords),
switches,
number_lines,
preserve_indent,
retain_labels,
use_labels,
label_format,
contents,
},
))
}
@@ -174,9 +212,15 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportBlock<'s>> {
let (remaining, name) = lesser_block_begin("export")(context, input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("export")(context, remaining)?;
// 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.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, export_type) = opt(map(
tuple((space1, switch_word, peek(tuple((space0, line_ending))))),
|(_, export_type, _)| export_type,
))(remaining)?;
let (remaining, parameters) =
opt(map(tuple((space1, data)), |(_, parameters)| parameters))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("export");
let contexts = [
@@ -190,22 +234,21 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
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]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
ExportBlock {
source: source.into(),
name: name.into(),
name: get_name(&affiliated_keywords),
export_type: export_type.map(Into::<&str>::into),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents: contents.into(),
contents,
},
))
}
@@ -215,9 +258,15 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> {
let (remaining, name) = lesser_block_begin("src")(context, input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("src")(context, remaining)?;
// 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.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, language) =
opt(map(tuple((space1, switch_word)), |(_, language)| language))(remaining)?;
let (remaining, switches) = opt(src_switches)(remaining)?;
let (remaining, parameters) = opt(map(tuple((space1, src_parameters)), |(_, parameters)| {
parameters
}))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("src");
let contexts = [
@@ -231,22 +280,44 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>(
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]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
if let Some(switches) = switches {
(
if switches.source.len() == 0 {
None
} else {
Some(switches.source)
},
switches.number_lines,
switches.preserve_indent,
switches.retain_labels,
switches.use_labels,
switches.label_format,
)
} else {
(None, None, None, RetainLabels::Yes, true, None)
}
};
Ok((
remaining,
SrcBlock {
source: source.into(),
name: name.into(),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents: contents.into(),
name: get_name(&affiliated_keywords),
language: language.map(Into::<&str>::into),
switches,
parameters: parameters.map(Into::<&str>::into),
number_lines,
preserve_indent,
retain_labels,
use_labels,
label_format,
contents,
},
))
}
@@ -309,3 +380,259 @@ fn _lesser_block_begin<'b, 'g, 'r, 's, 'c>(
))(remaining)?;
Ok((remaining, name))
}
#[derive(Debug)]
struct ExampleSrcSwitches<'s> {
source: &'s str,
number_lines: Option<SwitchNumberLines>,
retain_labels: RetainLabels,
preserve_indent: Option<CharOffsetInLine>,
use_labels: bool,
label_format: Option<&'s str>,
}
#[derive(Debug)]
enum SwitchState {
Normal,
NewLineNumber,
ContinuedLineNumber,
LabelFormat,
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn src_parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(
recognize(many_till(anychar, peek(tuple((space0, line_ending))))),
|parameters: &OrgSource<'_>| parameters.len() > 0,
)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn src_switches<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
let (remaining, leading_spaces) = space1(input)?;
let offset = Into::<&str>::into(leading_spaces).chars().count();
let offset = CharOffsetInLine::try_from(offset)
.expect("Character offset should fit in CharOffsetInLine");
example_src_switches(true, offset)(remaining)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn example_switches<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
let (remaining, switches) = example_src_switches(false, 0)(input)?;
Ok((remaining, switches))
}
fn example_src_switches(
stop_at_parameters: bool,
additional_char_offset: CharOffsetInLine,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
move |input| _example_src_switches(input, stop_at_parameters, additional_char_offset)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _example_src_switches<'s>(
input: OrgSource<'s>,
stop_at_parameters: bool,
additional_char_offset: CharOffsetInLine,
) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
let mut number_lines = None;
let mut retain_labels = RetainLabels::Yes;
let mut preserve_indent = None;
let mut use_labels = true;
let mut label_format = None;
let mut saw_r = false;
let mut matched_a_word = false;
let mut remaining = input;
let mut last_match_remaining = input;
let mut state = SwitchState::Normal;
'outer: loop {
let (remain, word) = opt(switch_word)(remaining)?;
let word = match word {
Some(word) => word,
None => {
break;
}
};
let normalized_word = Into::<&str>::into(word);
loop {
match (&state, normalized_word) {
(SwitchState::Normal, "-n") => {
state = SwitchState::NewLineNumber;
}
(SwitchState::Normal, "+n") => {
state = SwitchState::ContinuedLineNumber;
}
(SwitchState::Normal, "-r") => {
saw_r = true;
use_labels = false;
match retain_labels {
RetainLabels::Yes => {
retain_labels = RetainLabels::No;
}
_ => {}
}
}
(SwitchState::Normal, "-l") => {
state = SwitchState::LabelFormat;
}
(SwitchState::Normal, "-k") => {
use_labels = false;
let text_until_flag = input.get_until(word);
let character_offset = Into::<&str>::into(text_until_flag).chars().count();
let character_offset = CharOffsetInLine::try_from(character_offset)
.expect("Character offset should fit in CharOffsetInLine");
retain_labels = RetainLabels::Keep(character_offset + additional_char_offset);
}
(SwitchState::Normal, "-i") => {
let text_until_flag = input.get_until(word);
let character_offset = Into::<&str>::into(text_until_flag).chars().count();
let character_offset = CharOffsetInLine::try_from(character_offset)
.expect("Character offset should fit in CharOffsetInLine");
preserve_indent = Some(character_offset + additional_char_offset);
}
(SwitchState::NewLineNumber, _) => {
let val = normalized_word.parse::<LineNumber>();
if let Ok(val) = val {
if val < 0 {
number_lines = Some(SwitchNumberLines::New(0));
} else {
// Note that this can result in a negative 1 if the val is originally 0.
number_lines = Some(SwitchNumberLines::New(val - 1));
}
state = SwitchState::Normal;
} else {
number_lines = Some(SwitchNumberLines::New(0));
state = SwitchState::Normal;
continue; // Re-processes the word
}
}
(SwitchState::ContinuedLineNumber, _) => {
let val = normalized_word.parse::<LineNumber>();
if let Ok(val) = val {
if val < 0 {
number_lines = Some(SwitchNumberLines::Continued(0));
} else {
// Note that this can result in a negative 1 if the val is originally 0.
number_lines = Some(SwitchNumberLines::Continued(val - 1));
}
state = SwitchState::Normal;
} else {
number_lines = Some(SwitchNumberLines::Continued(0));
state = SwitchState::Normal;
continue; // Re-processes the word
}
}
(SwitchState::LabelFormat, _) => {
label_format = Some(normalized_word);
state = SwitchState::Normal;
}
(SwitchState::Normal, _) if stop_at_parameters => {
break 'outer;
}
(SwitchState::Normal, _) => {}
};
matched_a_word = true;
remaining = remain;
last_match_remaining = remain;
let (remain, divider) = opt(space1)(remaining)?;
if divider.is_none() {
break 'outer;
}
remaining = remain;
break;
}
}
if !matched_a_word {
return Err(nom::Err::Error(CustomError::MyError(MyError("No words."))));
}
let remaining = last_match_remaining;
let (remaining, _post_spaces) = opt(tuple((space0, peek(line_ending))))(remaining)?;
let source = input.get_until(remaining);
// Handle state that didn't get processed because we ran out of words.
match state {
SwitchState::Normal => {}
SwitchState::NewLineNumber => {
number_lines = Some(SwitchNumberLines::New(0));
}
SwitchState::ContinuedLineNumber => {
number_lines = Some(SwitchNumberLines::Continued(0));
}
SwitchState::LabelFormat => {}
}
let retain_labels = match retain_labels {
RetainLabels::Keep(_) if !saw_r => RetainLabels::Yes,
_ => retain_labels,
};
Ok((
remaining,
ExampleSrcSwitches {
source: Into::<&str>::into(source),
number_lines,
retain_labels,
preserve_indent,
use_labels,
label_format,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
map(
tuple((tag(r#"""#), is_not("\"\r\n"), tag(r#"""#))),
|(_, contents, _)| contents,
),
is_not(" \t\r\n"),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn content<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, String> {
let mut ret = String::new();
let mut remaining = input;
let exit_matcher_parser = parser_with_context!(exit_matcher_parser)(context);
loop {
if exit_matcher_parser(remaining).is_ok() {
break;
}
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
pre_escape_whitespace.map(|val| ret.push_str(Into::<&str>::into(val)));
ret.push_str(line.into());
remaining = remain;
}
Ok((remaining, ret))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn content_line<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (Option<OrgSource<'s>>, OrgSource<'s>)> {
let (remaining, pre_escape_whitespace) = opt(map(
tuple((
recognize(tuple((
space0,
many_till(
tag(","),
peek(tuple((tag(","), alt((tag("#+"), tag("*")))))),
),
))),
tag(","),
)),
|(pre_comma, _)| pre_comma,
))(input)?;
let (remaining, line_post_escape) = recognize(many_till(anychar, line_ending))(remaining)?;
Ok((remaining, (pre_escape_whitespace, line_post_escape)))
}

View File

@@ -1,4 +1,5 @@
mod angle_link;
mod babel_call;
mod citation;
mod citation_reference;
mod clock;

View File

@@ -14,7 +14,7 @@ use crate::error::MyError;
pub(crate) type BracketDepth = i16;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq)]
pub(crate) struct OrgSource<'s> {
full_source: &'s str,
start: usize,

View File

@@ -2,19 +2,27 @@ use nom::branch::alt;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::element_parser::detect_element;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::blank_line;
use super::util::get_consumed;
use super::util::get_has_affiliated_keyword;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::HasAffiliatedKeywordInner;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::exit_matcher_parser;
@@ -26,27 +34,38 @@ pub(crate) fn paragraph<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &paragraph_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let contexts = [
ContextElement::HasAffiliatedKeyword(HasAffiliatedKeywordInner {
start_after_affiliated_keywords: remaining,
keywords: &affiliated_keywords,
}),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &paragraph_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) = verify(
many_till(standard_set_object_matcher, exit_matcher),
|(children, _exit_contents)| !children.is_empty(),
)(input)?;
)(remaining)?;
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph {
source: source.into(),
name: get_name(&affiliated_keywords),
children,
},
))
@@ -57,10 +76,21 @@ fn paragraph_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let non_paragraph_element_matcher = parser_with_context!(detect_element(false))(context);
let regular_end = recognize(tuple((start_of_line, many1(blank_line))))(input);
if regular_end.is_ok() {
return regular_end;
}
match get_has_affiliated_keyword(context) {
Some(start_post_affiliated_keywords) if input == start_post_affiliated_keywords => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No exit due to affiliated keywords.",
))));
}
_ => {}
}
// Check to see if input is the start of a HasAffiliatedKeyword
alt((
recognize(tuple((start_of_line, many1(blank_line)))),
recognize(non_paragraph_element_matcher),
recognize(parser_with_context!(detect_element(false))(context)),
eof,
))(input)
}

View File

@@ -19,8 +19,10 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::element_parser::element;
use super::keyword::affiliated_keyword;
use super::object_parser::standard_set_object;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::include_input;
use super::util::indentation_level;
use super::util::non_whitespace_character;
@@ -53,6 +55,7 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
if verify(
tuple((
start_of_line,
@@ -78,6 +81,8 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainList<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let contexts = [
ContextElement::Context("plain list"),
ContextElement::ConsumeTrailingWhitespace(true),
@@ -94,7 +99,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
let mut children = Vec::new();
let mut first_item_indentation: Option<IndentationLevel> = None;
let mut first_item_list_type: Option<PlainListType> = None;
let mut remaining = input;
let mut remaining = remaining;
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
//
@@ -145,11 +150,14 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
children.push((final_child_start, reparsed_final_item));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
PlainList {
source: source.into(),
name: get_name(&affiliated_keywords),
list_type: first_item_list_type.expect("Plain lists require at least one element."),
children: children.into_iter().map(|(_start, item)| item).collect(),
},

View File

@@ -120,7 +120,7 @@ fn node_property<'b, 'g, 'r, 's>(
remaining,
NodeProperty {
source: source.into(),
name: Into::<&str>::into(name),
property_name: Into::<&str>::into(name),
value: None,
},
))
@@ -133,7 +133,7 @@ fn node_property<'b, 'g, 'r, 's>(
remaining,
NodeProperty {
source: source.into(),
name: Into::<&str>::into(name),
property_name: Into::<&str>::into(name),
value: Some(value.into()),
},
))

View File

@@ -13,10 +13,13 @@ use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource;
use super::util::exit_matcher_parser;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
@@ -38,8 +41,9 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Table<'s>> {
start_of_line(input)?;
peek(tuple((space0, tag("|"))))(input)?;
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
peek(tuple((space0, tag("|"))))(remaining)?;
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
@@ -57,17 +61,20 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(org_mode_table_row_matcher, exit_matcher)(input)?;
many_till(org_mode_table_row_matcher, exit_matcher)(remaining)?;
let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Table {
source: source.into(),
name: get_name(&affiliated_keywords),
formulas,
children,
},
@@ -76,6 +83,7 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, space0, tag("|")))(input)?;
Ok((input, ()))
}

View File

@@ -165,7 +165,7 @@ fn active_timestamp<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'b, 'g, 'r, 's>(
pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
@@ -299,7 +299,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
@@ -332,7 +332,7 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {

View File

@@ -1,5 +1,4 @@
use nom::branch::alt;
use nom::bytes::complete::is_a;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::none_of;
@@ -22,6 +21,7 @@ use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::types::IndentationLevel;
use crate::types::Keyword;
pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -229,13 +229,6 @@ where
}
}
/// Match at least one space character.
///
/// This is similar to nom's space1 parser except space1 matches both spaces and tabs whereas this only matches spaces.
pub(crate) fn only_space1<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_a(" ")(input)
}
/// Match single space or tab.
///
/// In org-mode syntax, spaces and tabs are often (but not always!) interchangeable.
@@ -275,3 +268,36 @@ pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
.sum();
Ok((remaining, (indentation_level, leading_whitespace)))
}
pub(crate) fn get_name<'s>(affiliated_keywords: &Vec<Keyword<'s>>) -> Option<&'s str> {
let name_keyword = affiliated_keywords
.iter()
.filter(|kw| {
kw.key.eq_ignore_ascii_case("name")
|| kw.key.eq_ignore_ascii_case("source")
|| kw.key.eq_ignore_ascii_case("tblname")
|| kw.key.eq_ignore_ascii_case("resname")
|| kw.key.eq_ignore_ascii_case("srcname")
|| kw.key.eq_ignore_ascii_case("label")
})
.last();
name_keyword.map(|kw| kw.value)
}
pub(crate) fn get_has_affiliated_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
) -> Option<OrgSource<'s>> {
for context in context.iter() {
match context {
ContextElement::HasAffiliatedKeyword(inner) => {
if !inner.keywords.is_empty() {
return Some(inner.start_after_affiliated_keywords);
} else {
return None;
}
}
_ => {}
}
}
None
}

View File

@@ -7,6 +7,7 @@ use super::StandardProperties;
#[derive(Debug)]
pub struct PlainList<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub list_type: PlainListType,
pub children: Vec<PlainListItem<'s>>,
}
@@ -46,19 +47,22 @@ pub enum CheckboxType {
#[derive(Debug)]
pub struct CenterBlock<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub struct QuoteBlock<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub struct SpecialBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub block_type: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
@@ -66,7 +70,8 @@ pub struct SpecialBlock<'s> {
#[derive(Debug)]
pub struct DynamicBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub block_name: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
@@ -74,6 +79,7 @@ pub struct DynamicBlock<'s> {
#[derive(Debug)]
pub struct FootnoteDefinition<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub label: &'s str,
pub children: Vec<Element<'s>>,
}
@@ -81,7 +87,8 @@ pub struct FootnoteDefinition<'s> {
#[derive(Debug)]
pub struct Drawer<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub drawer_name: &'s str,
pub children: Vec<Element<'s>>,
}
@@ -94,13 +101,14 @@ pub struct PropertyDrawer<'s> {
#[derive(Debug)]
pub struct NodeProperty<'s> {
pub source: &'s str,
pub name: &'s str,
pub property_name: &'s str,
pub value: Option<&'s str>,
}
#[derive(Debug)]
pub struct Table<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub formulas: Vec<Keyword<'s>>,
pub children: Vec<TableRow<'s>>,
}

View File

@@ -6,6 +6,7 @@ use super::Timestamp;
#[derive(Debug)]
pub struct Paragraph<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
@@ -24,7 +25,7 @@ pub struct TableCell<'s> {
#[derive(Debug)]
pub struct VerseBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub data: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
@@ -32,43 +33,76 @@ pub struct VerseBlock<'s> {
#[derive(Debug)]
pub struct CommentBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub data: Option<&'s str>,
pub name: Option<&'s str>,
pub contents: &'s str,
}
pub type CharOffsetInLine = u16;
#[derive(Debug)]
pub enum RetainLabels {
No,
Yes,
/// When adding -k to the switches on an example or src block, the labels are kept in the source code and links will use line numbers.
Keep(CharOffsetInLine),
}
#[derive(Debug)]
pub struct ExampleBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub data: Option<&'s str>,
pub contents: &'s str,
pub name: Option<&'s str>,
pub switches: Option<&'s str>,
pub number_lines: Option<SwitchNumberLines>,
pub preserve_indent: Option<CharOffsetInLine>,
pub retain_labels: RetainLabels,
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub contents: String,
}
#[derive(Debug)]
pub struct ExportBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub export_type: Option<&'s str>,
pub data: Option<&'s str>,
pub contents: &'s str,
pub contents: String,
}
#[derive(Debug)]
pub struct SrcBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub data: Option<&'s str>,
pub contents: &'s str,
pub name: Option<&'s str>,
pub language: Option<&'s str>,
pub switches: Option<&'s str>,
pub parameters: Option<&'s str>,
pub number_lines: Option<SwitchNumberLines>,
pub preserve_indent: Option<CharOffsetInLine>,
pub retain_labels: RetainLabels,
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub contents: String,
}
#[derive(Debug)]
pub enum ClockStatus {
Running,
Closed,
}
#[derive(Debug)]
pub struct Clock<'s> {
pub source: &'s str,
pub timestamp: Timestamp<'s>,
pub duration: Option<&'s str>,
pub status: ClockStatus,
}
#[derive(Debug)]
pub struct DiarySexp<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub value: &'s str,
}
#[derive(Debug)]
@@ -82,16 +116,20 @@ pub struct Planning<'s> {
#[derive(Debug)]
pub struct FixedWidthArea<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub value: Vec<&'s str>,
}
#[derive(Debug)]
pub struct HorizontalRule<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
}
#[derive(Debug)]
pub struct Keyword<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub key: &'s str,
pub value: &'s str,
}
@@ -99,21 +137,42 @@ pub struct Keyword<'s> {
#[derive(Debug)]
pub struct BabelCall<'s> {
pub source: &'s str,
pub key: &'s str,
pub name: Option<&'s str>,
pub value: &'s str,
pub call: Option<&'s str>,
pub inside_header: Option<&'s str>,
pub arguments: Option<&'s str>,
pub end_header: Option<&'s str>,
}
#[derive(Debug)]
pub struct LatexEnvironment<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub value: &'s str,
}
/// A line number used in switches to lesser blocks.
///
/// This must be signed because emacs subtracts 1 from the actual value in the org-mode text, which makes a 0 turn into a -1.
pub type LineNumber = isize;
#[derive(Debug)]
pub enum SwitchNumberLines {
New(LineNumber),
Continued(LineNumber),
}
impl<'s> Paragraph<'s> {
/// Generate a paragraph of the passed in text with no additional properties.
///
/// This is used for elements that support an "empty" content like greater blocks.
pub(crate) fn of_text(input: &'s str) -> Self {
let mut objects = Vec::with_capacity(1);
objects.push(Object::PlainText(PlainText { source: input }));
Paragraph {
source: input,
name: None,
children: objects,
}
}
@@ -213,7 +272,6 @@ impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
impl<'s> Comment<'s> {
pub fn get_value(&self) -> String {
// TODO: maybe we should handle parsing here instead of storing the parsing result in the AST since I imagine getting the value of comments won't be a common operation.
let final_size = self.value.iter().map(|line| line.len()).sum();
let mut ret = String::with_capacity(final_size);
for line in &self.value {
@@ -223,3 +281,24 @@ impl<'s> Comment<'s> {
ret
}
}
impl<'s> FixedWidthArea<'s> {
pub fn get_value(&self) -> String {
let final_size = self.value.iter().map(|line| line.len()).sum();
let mut ret = String::with_capacity(final_size);
for line in &self.value {
ret.push_str(line);
}
ret
}
}
impl<'s> ExportBlock<'s> {
/// Gets the export type capitalized.
///
/// Upstream Emacs Org-mode capitalizes the export type.
pub fn get_export_type(&self) -> Option<String> {
self.export_type.map(|s| s.to_uppercase())
}
}

View File

@@ -37,7 +37,9 @@ pub use greater_element::Table;
pub use greater_element::TableRow;
pub use greater_element::TableRowType;
pub use lesser_element::BabelCall;
pub use lesser_element::CharOffsetInLine;
pub use lesser_element::Clock;
pub use lesser_element::ClockStatus;
pub use lesser_element::Comment;
pub use lesser_element::CommentBlock;
pub use lesser_element::DiarySexp;
@@ -47,9 +49,12 @@ pub use lesser_element::FixedWidthArea;
pub use lesser_element::HorizontalRule;
pub use lesser_element::Keyword;
pub use lesser_element::LatexEnvironment;
pub use lesser_element::LineNumber;
pub use lesser_element::Paragraph;
pub use lesser_element::Planning;
pub use lesser_element::RetainLabels;
pub use lesser_element::SrcBlock;
pub use lesser_element::SwitchNumberLines;
pub use lesser_element::TableCell;
pub use lesser_element::VerseBlock;
pub use object::AngleLink;