108 Commits

Author SHA1 Message Date
Tom Alexander
5694f80f41 Merge branch 'radio_target_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-07 01:43:58 -04:00
Tom Alexander
36327e92d7 Radio link did not need Cow.
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
I was only using Cow because regular links can use templates which would require owned data. Radio links do not share this feature.
2023-10-07 01:26:52 -04:00
Tom Alexander
c70eb69ed6 Compare radio target properties. 2023-10-07 01:24:32 -04:00
Tom Alexander
8d9ff77799 Merge branch 'radio_link_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-07 01:14:12 -04:00
Tom Alexander
029791e0b2 Fix tests. 2023-10-07 01:01:04 -04:00
Tom Alexander
8d621b32dc Compare radio link properties.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-07 00:57:55 -04:00
Tom Alexander
dfad7b7888 Merge branch 'regular_link_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-07 00:34:14 -04:00
Tom Alexander
56348a6d54 Actually absolute paths are files too.
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-07 00:15:37 -04:00
Tom Alexander
65e142a215 Link templates support single-colons and inject the value at the end if no %s is found. 2023-10-06 23:55:21 -04:00
Tom Alexander
aa0a0b890e Relative paths are file links, absolute paths are fuzzy. 2023-10-06 23:48:55 -04:00
Tom Alexander
ab33730830 Search option also supports regex.
Some checks failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
As seen on the list of possible search options at https://orgmode.org/manual/Search-Options.html.
2023-10-06 23:33:09 -04:00
Tom Alexander
4c8828b91b Apply the link templates. 2023-10-06 23:33:09 -04:00
Tom Alexander
2ba5156ee1 Add a global setting for link templates. 2023-10-06 20:34:13 -04:00
Tom Alexander
ae3a6ff919 Turns out links can have templates. 2023-10-06 20:23:19 -04:00
Tom Alexander
4716e1ce5b Switch to using AsRef<str> to make compare_property_quoted_string generic. 2023-10-06 20:23:19 -04:00
Tom Alexander
89fcf6cb54 Restrict protocol links to org-link-parameters. 2023-10-06 19:18:58 -04:00
Tom Alexander
f220fd63e5 Add org-link-parameters to the GlobalSettings. 2023-10-06 19:14:11 -04:00
Tom Alexander
7cf1f7b7bb Only orgify the link text if there are line breaks present. 2023-10-06 19:05:56 -04:00
Tom Alexander
135ca133ea Add tests for search option. 2023-10-06 18:56:11 -04:00
Tom Alexander
d126488891 Handle orgifying text in regular link path and raw-link. 2023-10-06 18:30:08 -04:00
Tom Alexander
51748afd41 Compare pathreg. 2023-10-06 17:28:26 -04:00
Tom Alexander
e84135985e Parse out the pathreg on regular links. 2023-10-06 17:21:43 -04:00
Tom Alexander
448902bb05 Add regular link tests for all the types. 2023-10-06 16:52:45 -04:00
Tom Alexander
65df18171a Add field list for regular link. 2023-10-06 16:42:31 -04:00
Tom Alexander
e2bc58a469 Merge branch 'compare_fields_macro'
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 16:36:19 -04:00
Tom Alexander
fbe3c76ab7 Cleanup. 2023-10-06 16:32:49 -04:00
Tom Alexander
368c6a457e Wrap the parameter sets in parenthesis. 2023-10-06 16:19:43 -04:00
Tom Alexander
c7dbe596b3 Switch to more generic implementation. 2023-10-06 16:03:41 -04:00
Tom Alexander
3da52a0826 Make a more generic version of compare_property_quoted_string.
This allows for the rust value to be determined by a function rather than hard-coded.
2023-10-06 14:07:25 -04:00
Tom Alexander
ae11e390d1 Add a default case for tokens which do not have any expected properties except for :standard-properties. 2023-10-06 13:45:19 -04:00
Tom Alexander
45dd38ac2d Assume :standard-properties is an expected field. 2023-10-06 13:40:11 -04:00
Tom Alexander
7af18e2312 Move the EmacsField enum since it has nothing to do with ElispFact. 2023-10-06 13:32:39 -04:00
Tom Alexander
1b603f3a05 Implement the comparison. 2023-10-06 13:29:46 -04:00
Tom Alexander
d06e4de7b0 Starting a new macro for comparing fields.
This will help us assert that all fields are tested and that only expected fields are present.
2023-10-06 13:08:15 -04:00
Tom Alexander
e686666ea0 Merge branch 'text_markup_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:32:05 -04:00
Tom Alexander
672848d06a Compare the properties of text markup. 2023-10-06 12:12:24 -04:00
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
111 changed files with 3267 additions and 403 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

@@ -0,0 +1,13 @@
[[(foo)]]
[[((bar))]]
[[((baz)]]
[[(lo
rem)]]
# These become fuzzy
[[(foo) ]]
[[ (foo)]]
[[(foo)::3]]

View File

@@ -0,0 +1,6 @@
[[#foo]]
[[#fo
o]]
[[#foo::3]]

View File

@@ -0,0 +1,21 @@
[[./simple.org]]
[[../simple.org]]
[[/simple.org]]
[[file:simple.org]]
[[file:sim ple.org]]
[[file:simp
le.org]]
[[file:simple.org::3]]
[[file:simple.org::foo]]
[[file:simple.org::#foo]]
[[file:simple.org::foo bar]]
[[file:simple.org::foo
bar]]
[[file:simple.org::foo
bar]]
[[file:simple.org::foo
bar]]
[[file:simple.org::foo::bar]]
[[file:simple.org::/foo/]]

View File

@@ -0,0 +1,6 @@
[[elisp.org]]
[[eli
sp.org]]
[[elisp.org::3]]

View File

@@ -0,0 +1,6 @@
[[id:83986bdf-987c-465d-8851-44cb4c02a86c]]
[[id:83986bdf-987c-465d
-8851-44cb4c02a86c]]
[[id:83986bdf-987c-465d-8851-44cb4c02a86c::foo]]

View File

@@ -0,0 +1,6 @@
[[shell:foo]]
[[shell:fo
o]]
[[shell:foo::3]]

View File

@@ -0,0 +1,7 @@
#+LINK: foo https://foo.bar/baz#%s
[[foo::lorem]]
[[foo::ipsum][dolar]]
[[cat::bat]]
#+LINK: cat dog%s
[[cat:bat]]

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(())

View File

@@ -0,0 +1,104 @@
use std::fmt::Debug;
use super::diff::DiffStatus;
use super::sexp::Token;
use super::util::get_property;
use super::util::get_property_quoted_string;
use super::util::get_property_unquoted_atom;
#[derive(Debug)]
pub(crate) enum EmacsField<'s> {
Required(&'s str),
#[allow(dead_code)]
Optional(&'s str),
}
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
#[allow(dead_code)]
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
_emacs: &'b Token<'s>,
_rust_node: R,
_emacs_field: &'x str,
_rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
Ok(None)
}
/// Do no comparison.
///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
#[allow(dead_code)]
pub(crate) fn compare_identity() -> () {
()
}
/// Assert that the emacs value is always nil or absent.
///
/// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property.
pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
emacs: &'b Token<'s>,
_rust_node: R,
emacs_field: &'x str,
_rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
let value = get_property(emacs, emacs_field)?;
if value.is_some() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} was expected to always be nil: {:?}",
emacs_field, value
));
Ok(Some((this_status, message)))
} else {
Ok(None)
}
}
pub(crate) fn compare_property_quoted_string<
'b,
's,
'x,
R,
RV: AsRef<str> + std::fmt::Debug,
RG: Fn(R) -> Option<RV>,
>(
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
let value = get_property_quoted_string(emacs, emacs_field)?;
let rust_value = rust_value_getter(rust_node);
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_ref().map(String::as_str) {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
Ok(Some((this_status, message)))
} else {
Ok(None)
}
}
pub(crate) fn compare_property_unquoted_atom<'b, 's, 'x, R, RG: Fn(R) -> Option<&'s str>>(
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
let value = get_property_unquoted_atom(emacs, emacs_field)?;
let rust_value = rust_value_getter(rust_node);
if rust_value != value {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
Ok(Some((this_status, message)))
} else {
Ok(None)
}
}

File diff suppressed because it is too large Load Diff

155
src/compare/macros.rs Normal file
View File

@@ -0,0 +1,155 @@
/// Assert only the listed properties exist on the Emacs AST node and compare their values to the values on the rust AST node.
///
/// Example invocation:
/// ```
/// if let Some((new_status, new_message)) = compare_properties!(
/// emacs,
/// rust,
/// (
/// EmacsField::Required(":key"),
/// |r| Some(r.key),
/// compare_property_quoted_string
/// ),
/// (
/// EmacsField::Required(":value"),
/// |r| Some(r.contents),
/// compare_property_quoted_string
/// ),
/// (EmacsField::Required(":pre-blank"), compare_identity, compare_noop)
/// )? {
/// this_status = new_status;
/// message = new_message;
/// }
/// ```
///
/// Or if the node has no properties aside from :standard-properties, we can still assert that the node has no unexpected properties:
/// ```
/// if let Some((new_status, new_message)) = compare_properties!(emacs)? {
/// this_status = new_status;
/// message = new_message;
/// }
/// ```
macro_rules! compare_properties {
($emacs:expr, $rust:expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),+) => {
{
let mut this_status = DiffStatus::Good;
let mut message: Option<String> = None;
let children = $emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let mut emacs_keys: BTreeSet<&str> = attributes_map.keys().map(|s| *s).collect();
if emacs_keys.contains(":standard-properties") {
emacs_keys.remove(":standard-properties");
} else {
this_status = DiffStatus::Bad;
message = Some(format!(
"Emacs token lacks :standard-properties field.",
));
}
$(
match $emacs_field {
EmacsField::Required(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Optional(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Required(name) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Emacs token lacks required field: {}",
name
));
},
EmacsField::Optional(_name) => {},
}
)+
if !emacs_keys.is_empty() {
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
let unexpected_keys = unexpected_keys.join(", ");
this_status = DiffStatus::Bad;
message = Some(format!(
"Emacs token had extra field(s): {}",
unexpected_keys
));
}
$(
let emacs_name = match $emacs_field {
EmacsField::Required(name) => {
name
},
EmacsField::Optional(name) => {
name
},
};
let result = $compare_fn($emacs, $rust, emacs_name, $rust_value_getter)?;
match result {
Some((DiffStatus::Good, _)) => unreachable!("No comparison functions should return Some() when DiffStatus is good."),
Some((status, msg)) => {
this_status = status;
message = msg;
},
_ => {}
}
)+
match this_status {
DiffStatus::Good => {
let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
result
},
_ => {
Ok(Some((this_status, message)))
}
}
}
};
// Default case for when there are no expected properties except for :standard-properties
($emacs:expr) => {
{
let mut this_status = DiffStatus::Good;
let mut message: Option<String> = None;
let children = $emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let mut emacs_keys: BTreeSet<&str> = attributes_map.keys().map(|s| *s).collect();
if emacs_keys.contains(":standard-properties") {
emacs_keys.remove(":standard-properties");
} else {
this_status = DiffStatus::Bad;
message = Some(format!(
"Emacs token lacks :standard-properties field.",
));
}
if !emacs_keys.is_empty() {
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
let unexpected_keys = unexpected_keys.join(", ");
this_status = DiffStatus::Bad;
message = Some(format!(
"Emacs token had extra field(s): {}",
unexpected_keys
));
}
match this_status {
DiffStatus::Good => {
let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
result
},
_ => {
Ok(Some((this_status, message)))
}
}
}
};
}
pub(crate) use compare_properties;

View File

@@ -1,6 +1,8 @@
mod compare;
mod compare_field;
mod diff;
mod elisp_fact;
mod macros;
mod parse;
mod sexp;
mod util;

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

@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use super::FileAccessInterface;
@@ -32,6 +33,23 @@ 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,
/// The allowed protocols for links (for example, the "https" in "https://foo.bar/").
///
/// Corresponds to org-link-parameters elisp variable.
pub link_parameters: &'g [&'g str],
/// Link templates where the key is the document text and the value is the replacement.
///
/// For example, `"foo": "bar%s"` will replace `[[foo::baz]]` with `[[barbaz]]`
///
/// This is set by including #+LINK in the org-mode document.
pub link_templates: BTreeMap<String, String>,
}
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@@ -49,6 +67,9 @@ impl<'g, 's> GlobalSettings<'g, 's> {
tab_width: DEFAULT_TAB_WIDTH,
odd_levels_only: HeadlineLevelFilter::default(),
footnote_section: "Footnotes",
coderef_label_format: "(ref:%s)",
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
link_templates: BTreeMap::new(),
}
}
}
@@ -70,3 +91,29 @@ impl Default for HeadlineLevelFilter {
HeadlineLevelFilter::OddEven
}
}
const DEFAULT_ORG_LINK_PARAMETERS: [&'static str; 23] = [
"id",
"eww",
"rmail",
"mhe",
"irc",
"info",
"gnus",
"docview",
"bibtex",
"bbdb",
"w3m",
"doi",
"file+sys",
"file+emacs",
"shell",
"news",
"mailto",
"https",
"http",
"ftp",
"help",
"file",
"elisp",
];

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

@@ -131,7 +131,8 @@ fn document_org_source<'b, 'g, 'r, 's>(
.collect::<Result<Vec<_>, _>>()?;
for setup_file in setup_files.iter().map(String::as_str) {
let (_, setup_file_settings) =
scan_for_in_buffer_settings(setup_file.into()).map_err(|_err| {
scan_for_in_buffer_settings(setup_file.into()).map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
@@ -141,7 +142,8 @@ fn document_org_source<'b, 'g, 'r, 's>(
}
final_settings.extend(document_settings);
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
.map_err(|_err| {
.map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),

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,14 +74,17 @@ 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((
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),
@@ -99,31 +104,68 @@ fn _element<'b, 'g, 'r, 's>(
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)
}
}
}?;
)))(input)?
};
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
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 source = get_consumed(input, remaining);
element.set_source(source.into());
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;
}
}
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 (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

@@ -2,8 +2,17 @@ use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_until;
use nom::character::complete::anychar;
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::peek;
use nom::combinator::recognize;
use nom::multi::many_till;
use nom::multi::separated_list0;
use nom::sequence::tuple;
use super::keyword::filtered_keyword;
use super::keyword_todo::todo_keywords;
@@ -75,6 +84,7 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>,
@@ -113,10 +123,22 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
}
}
// Link templates
for kw in keywords
.iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("link"))
{
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|e| e.to_string())?;
new_settings
.link_templates
.insert(link_key.to_owned(), link_value.to_owned());
}
Ok(new_settings)
}
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
document: &mut Document<'s>,
) -> Result<(), &'static str> {
@@ -135,6 +157,30 @@ pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
Ok(())
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn link_template<'s>(input: &'s str) -> Res<&'s str, (&'s str, &'s str)> {
let (remaining, key) = map(
tuple((
space0,
recognize(many_till(anychar, peek(alt((space1, line_ending, eof))))),
)),
|(_, key)| key,
)(input)?;
let (remaining, replacement) = map(
tuple((
space1,
recognize(many_till(
anychar,
peek(tuple((space0, alt((line_ending, eof))))),
)),
)),
|(_, replacement)| replacement,
)(remaining)?;
Ok((remaining, (key, replacement)))
}
#[cfg(test)]
mod tests {
use super::*;

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

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::ops::RangeBounds;
use nom::Compare;
@@ -14,7 +15,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,
@@ -184,6 +185,18 @@ impl<'s> From<OrgSource<'s>> for &'s str {
}
}
impl<'s> From<&OrgSource<'s>> for Cow<'s, str> {
fn from(value: &OrgSource<'s>) -> Self {
(&value.full_source[value.start..value.end]).into()
}
}
impl<'s> From<OrgSource<'s>> for Cow<'s, str> {
fn from(value: OrgSource<'s>) -> Self {
(&value.full_source[value.start..value.end]).into()
}
}
impl<'s, R> Slice<R> for OrgSource<'s>
where
R: RangeBounds<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 {
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(&parser_context);
}),
];
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

@@ -32,33 +32,6 @@ use crate::parser::util::get_consumed;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::PlainLink;
// TODO: Make this a user-provided variable corresponding to elisp's org-link-parameters
const ORG_LINK_PARAMETERS: [&'static str; 23] = [
"id",
"eww",
"rmail",
"mhe",
"irc",
"info",
"gnus",
"docview",
"bibtex",
"bbdb",
"w3m",
"doi",
"file+sys",
"file+emacs",
"shell",
"news",
"mailto",
"https",
"http",
"ftp",
"help",
"file",
"elisp",
];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn plain_link<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
@@ -113,12 +86,11 @@ fn post<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn protocol<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-link-parameters
for link_parameter in ORG_LINK_PARAMETERS {
let result = tag_no_case::<_, _, CustomError<_>>(link_parameter)(input);
for link_parameter in context.get_global_settings().link_parameters {
let result = tag_no_case::<_, _, CustomError<_>>(*link_parameter)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));

Some files were not shown because too many files have changed in this diff Show More