159 Commits

Author SHA1 Message Date
Tom Alexander
26f41b83aa Publish version 0.1.7.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-12 16:15:53 -04:00
Tom Alexander
e4c0e32536 Change public interface to return boxed dynamic error instead of String.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-12 15:52:01 -04:00
Tom Alexander
37e85158ea Reduce the exposed functions when compare feature is enabled. 2023-09-12 15:48:37 -04:00
Tom Alexander
6589a755a6 Merge branch 'reduce_pub'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 15:37:33 -04:00
Tom Alexander
a651b79e77 Move sexp into compare. 2023-09-11 15:37:20 -04:00
Tom Alexander
98de5e4ec5 Remove unused sexp parser entry point. 2023-09-11 15:07:52 -04:00
Tom Alexander
cf383fa394 Only include sexp module if compare feature is enabled.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 14:59:24 -04:00
Tom Alexander
84953c1669 Put back in needed pubs. 2023-09-11 14:59:23 -04:00
Tom Alexander
7650a9edff Remove all pub. 2023-09-11 13:11:08 -04:00
Tom Alexander
a74319d381 Add TODO. 2023-09-11 13:09:46 -04:00
Tom Alexander
7e57285ea7 Merge branch 'additional_detects'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 12:59:18 -04:00
Tom Alexander
f103d168d5 Update diary sexp parser to match org-mode's behavior. 2023-09-11 12:34:57 -04:00
Tom Alexander
f54081437a Add detect element functions for all elements that can be reasonably detected more efficiently than just parsing normally. 2023-09-11 12:28:15 -04:00
Tom Alexander
aa5988bc2f Re-enable a test.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 11:18:25 -04:00
Tom Alexander
76ca2b9762 Merge branch 'description_list_colon_in_tag'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 10:42:25 -04:00
Tom Alexander
1561e1e580 Update plain list item tag parser to allow double colon as long as its not the last one on that line. 2023-09-11 10:29:15 -04:00
Tom Alexander
1f11bfa2ec Join the plain list item tag end matchers again. 2023-09-11 10:13:22 -04:00
Tom Alexander
8440a3b256 Update test to show that we are not parsing description lists with double colon inside the tag correctly. 2023-09-11 10:01:50 -04:00
Tom Alexander
de7ad182b3 Make parse and compare their own binaries.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
This ensures that both code paths are capable of being parsed by rust-analyzer simultaneously and I think it will be less confusing to newcomers.
2023-09-09 04:21:34 -04:00
Tom Alexander
b75d9f5c91 Remove an outdated comment. 2023-09-09 00:00:12 -04:00
Tom Alexander
612744ebd0 Publish version 0.1.6.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-08 23:28:59 -04:00
Tom Alexander
1b4b8b4bdb Merge branch 'howard_abrams'
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-09-08 23:19:19 -04:00
Tom Alexander
5587e19f16 Cleanup. 2023-09-08 23:19:08 -04:00
Tom Alexander
80f7098f9b Compare table formulas. 2023-09-08 23:05:04 -04:00
Tom Alexander
84d2babda9 Parse table formulas. 2023-09-08 22:47:07 -04:00
Tom Alexander
cc56b79683 Add a test showing we're not handling table formulas. 2023-09-08 22:42:24 -04:00
Tom Alexander
0105b49d0d Handle empty statistics cookies. 2023-09-08 22:21:49 -04:00
Tom Alexander
d79035e14d Add a test showing we are not handling empty statistics cookies. 2023-09-08 22:21:19 -04:00
Tom Alexander
7545fb7e1a Support keywords with colons in the key and without a space between the colon and value. 2023-09-08 22:17:10 -04:00
Tom Alexander
f30069efe7 Add a test showing we're not handling colons in keyword keys correctly. 2023-09-08 21:59:02 -04:00
Tom Alexander
d1fe2f6b09 Update the rest of the scripts to work with relative paths. 2023-09-08 21:50:32 -04:00
Tom Alexander
21c60d1036 Do not consume trailing whitespace on the footnote definition's final element. 2023-09-08 21:30:03 -04:00
Tom Alexander
6a1bdd5fee Support blank lines before content in footnote definitions. 2023-09-08 21:11:47 -04:00
Tom Alexander
5d20d3e99b Add a test showing we are not handling empty space for footnote definitions correctly. 2023-09-08 20:28:21 -04:00
Tom Alexander
a8fbf01124 Handle tabs for plain list descriptions.
This bug probably exists in hundreds of places across the code base. I am going to have to write a "fuzzer" that replaces random whitespace with tabs to find them all.
2023-09-08 20:02:49 -04:00
Tom Alexander
344ef04453 Add tests showing we are not handling tabs appropriately for description list tags. 2023-09-08 19:53:58 -04:00
Tom Alexander
ceb722e476 Check exit matcher after each space consumed for object trailing whitespace.
Since description list tags need to end with a space unconsumed for " ::", we need to check the exit matcher after each space consumed.
2023-09-08 19:38:49 -04:00
Tom Alexander
b04341882c Add test showing that we are not handling trailing spaces in description list tags correctly. 2023-09-08 19:24:03 -04:00
Tom Alexander
494fe5cceb Handle contentless list items mid-document. 2023-09-08 19:01:46 -04:00
Tom Alexander
0110d23387 Update empty list test to show that we're not handling trailing whitespace for empty list items properly. 2023-09-08 18:41:57 -04:00
Tom Alexander
0d7a15bfeb Handle spaces after statistics cookies. 2023-09-08 18:35:33 -04:00
Tom Alexander
352c20d1d8 Fix run_docker_compare_bisect with relative paths. 2023-09-08 18:05:10 -04:00
Tom Alexander
f82d2aada1 Fix run_docker_compare with relative paths. 2023-09-08 18:03:50 -04:00
Tom Alexander
669da4073e Accept the end condition as a parameter to the plain text parser so it can adapt to the context. 2023-09-08 17:54:49 -04:00
Tom Alexander
0056657b65 Add a test showing the plain text parser is not handling subsets of objects like inside a table cell. 2023-09-08 17:27:02 -04:00
Tom Alexander
8780976c15 Consume trailing whitespace after planning. 2023-09-08 16:30:40 -04:00
Tom Alexander
dc8b8d08ab Add test showing we break on empty sections that contain a planning. 2023-09-08 16:25:18 -04:00
Tom Alexander
93d3d9471f Compare priority, archived, and commented in headlines. 2023-09-08 16:00:16 -04:00
Tom Alexander
c7c0deed74 Parse priority cookie and COMMENT from headlines. 2023-09-08 16:00:16 -04:00
Tom Alexander
b32c21eb1d Add a test for a comment heading. 2023-09-08 16:00:16 -04:00
Tom Alexander
2e6e6fdd2b Move sections to their own source file. 2023-09-08 15:08:16 -04:00
Tom Alexander
3cc2294387 Move headlines into their own file. 2023-09-08 15:05:42 -04:00
Tom Alexander
40f22034da Make the item tag exit matcher a lower class than all all others.
This is to allow for " :: " inside a description list item's tag if it is nested inside another object.
2023-09-08 14:37:30 -04:00
Tom Alexander
ab612f293f Update org-mode version. 2023-09-08 13:11:58 -04:00
Tom Alexander
57c2922e4a Add test showing problem is description list parser. 2023-09-08 12:50:51 -04:00
Tom Alexander
c2eb1f51c8 Support blank lines between nested headlines. 2023-09-08 12:41:48 -04:00
Tom Alexander
b0930df788 Support zero skipped text in OrgSource slicing. 2023-09-07 04:16:00 -04:00
Tom Alexander
69512f559a Fix end conditions for subscript and superscript. 2023-09-07 04:16:00 -04:00
Tom Alexander
76a81b73ac Add a detect object function similar to the detect element function. 2023-09-07 04:16:00 -04:00
Tom Alexander
ba291c6776 Unify two places checking if text was preceded by whitespace. 2023-09-07 04:16:00 -04:00
Tom Alexander
6b82b46e09 Prevent nesting of text markup of the same type.
This greatly reduces the amount of detect element calls that are occurring.
2023-09-07 04:15:59 -04:00
Tom Alexander
6676012eb1 Change footnote reference class to Gamma. 2023-09-07 04:15:59 -04:00
Tom Alexander
facbe716e9 Cleanup 2023-09-07 01:23:26 -04:00
Tom Alexander
827f3e1c98 Add the rest of the relevant howard abrams repos. 2023-09-06 21:37:09 -04:00
Tom Alexander
fcea7e5a4b Add howard abrams demo-it and the upstreeam doomemacs repo to compare. 2023-09-06 21:11:46 -04:00
Tom Alexander
dda2b1e69f Compare howard abrams hamacs. 2023-09-06 20:56:36 -04:00
Tom Alexander
f79d07a7c8 Compare howard abrams dotfiles. 2023-09-06 19:49:04 -04:00
Tom Alexander
45283b48d9 Update performance scripts to support taking input file parameters.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-06 19:31:09 -04:00
Tom Alexander
08e4c646e5 Merge branch 'full_foreign_compare'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 19:17:35 -04:00
Tom Alexander
f8b99ed235 Support counter set in plain list items. 2023-09-06 19:09:20 -04:00
Tom Alexander
6fc607cfe0 Compare node properties. 2023-09-06 18:54:47 -04:00
Tom Alexander
49afcf0db6 Support property nodes with colons in their key. 2023-09-06 18:54:01 -04:00
Tom Alexander
c4d7e646fc Support spaces after the end of a lesser block. 2023-09-06 18:54:01 -04:00
Tom Alexander
3fc3a5d1ef Add support for empty lesser blocks. 2023-09-06 18:11:57 -04:00
Tom Alexander
6e2fc362ea Add support for babel-call keywords. 2023-09-06 18:04:53 -04:00
Tom Alexander
90fa48661c Prefer the longer version of affiliated keywords. 2023-09-06 17:41:00 -04:00
Tom Alexander
5cefcd5fac Add in the emacs repository org-mode documents. 2023-09-06 17:06:17 -04:00
Tom Alexander
b83a103c17 Update the foreign document test to use all org-mode documents from the org-mode repository. 2023-09-06 17:02:56 -04:00
Tom Alexander
d90ff5891b Publish version 0.1.5.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 16:19:42 -04:00
Tom Alexander
a3c01805b8 Merge branch 'foreign_document_test'
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-09-06 16:17:52 -04:00
Tom Alexander
e3d755317d Add a top-level makefile target for running the foreign document test. 2023-09-06 16:17:42 -04:00
Tom Alexander
b89607fc8b Handle CARGO_TARGET_DIR not being set.
All checks were successful
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 16:12:41 -04:00
Tom Alexander
51c4e2b62a Only use local folder for docker context.
Some checks failed
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-06 16:03:59 -04:00
Tom Alexander
a6561d37fb Add the foreign document test to the CI.
Some checks failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 16:00:09 -04:00
Tom Alexander
4e8b3eb422 Introduce a foreign document test docker container.
This test will grab documents from external sources and compare Organic's parser vs the official org-mode parser to ensure they are parsing the same. This is so we do not introduce large irrelevant documents in the git history and so we do not introduce documents with restrictive licenses into the repository.
2023-09-06 15:54:25 -04:00
Tom Alexander
2c31590974 Add a script to bisect org-mode source to find the line that breaks the compare.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 15:08:58 -04:00
Tom Alexander
28b2d27054 Consume trailing whitespace after a plain link.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 14:39:31 -04:00
Tom Alexander
84edd10864 Change lesser block exit class to Alpha.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
When an example block is nested inside a list, this change allows for the contents of the example block to be on lines less indented than before.
2023-09-06 14:14:02 -04:00
Tom Alexander
728a79f9a4 Handle zero-width space in text markup.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 13:56:27 -04:00
Tom Alexander
ad4ef50669 Merge branch 'setupfile'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 12:43:01 -04:00
Tom Alexander
12cbb89861 Compare todo-type on headlines.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 12:39:03 -04:00
Tom Alexander
7c471ab32e Compare keyword keys and values. 2023-09-06 12:10:57 -04:00
Tom Alexander
400f53e440 Cleanup. 2023-09-06 11:53:21 -04:00
Tom Alexander
028aeb70aa Use the global settings todo keywords when parsing headlines. 2023-09-06 11:45:35 -04:00
Tom Alexander
70fafd801e Apply the TODO keyword settings. 2023-09-06 11:07:57 -04:00
Tom Alexander
bdba495f69 Add a parser for the todo keyword's value.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 09:24:59 -04:00
Tom Alexander
b0392ad6fb Trim the trailing space off keywords with values.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-04 22:35:46 -04:00
Tom Alexander
1c142b68c6 Make the parse entry point call the parse_with_settings entry point. 2023-09-04 22:11:56 -04:00
Tom Alexander
9060f9b26d Only do a single pre-pass on the full document pulling out both setupfile and all other in-buffer settings.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has failed
Previously we made a separate pass just to find setupfile and then we pulled the in-buffer settings from everything.
2023-09-04 22:05:59 -04:00
Tom Alexander
d3c733c5ad Take into account the source directory when parsing org-mode in Organic.
Previously only the emacs code was doing this.
2023-09-04 21:46:40 -04:00
Tom Alexander
275b4b53d1 Use a single function for finding all keywords. 2023-09-04 19:19:23 -04:00
Tom Alexander
d38e198258 Add a parse_with_settings function. 2023-09-04 17:44:27 -04:00
Tom Alexander
27cf6c0462 Remove unnecessary map_err from main.rs.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-04 17:29:50 -04:00
Tom Alexander
c7d5c89a60 Passing the setupfile contents to the document parser. 2023-09-04 17:16:08 -04:00
Tom Alexander
ee02e07717 Read the setup file into memory. 2023-09-04 16:53:02 -04:00
Tom Alexander
a7330e38e4 Enable dynamic access to the file access interface. 2023-09-04 16:29:41 -04:00
Tom Alexander
08eb59acd3 Rename parser_context to context. 2023-09-04 13:26:11 -04:00
Tom Alexander
da1ce2717d Introduce a file access interface for reading additional files. 2023-09-04 13:00:41 -04:00
Tom Alexander
a8f277efe5 Scan for setupfile at the beginning of a parse. 2023-09-04 12:48:59 -04:00
Tom Alexander
7f6f22717b Add comment. 2023-09-04 12:31:43 -04:00
Tom Alexander
0ef141d65e Switch to putting radio targets in the global settings instead of the context tree.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-04 12:28:33 -04:00
Tom Alexander
71180d19fb Fix reading contents from stdin in parse script.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-04 11:45:10 -04:00
Tom Alexander
33091112a5 Remove OrgSource from the public document parser interface.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-04 11:38:12 -04:00
Tom Alexander
5997567233 Merge branch 'stack_based_context'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-03 16:22:54 -04:00
Tom Alexander
2915a81edc Cleanup. 2023-09-03 16:22:41 -04:00
Tom Alexander
df79cbd0b7 Give global options their own lifetime. 2023-09-03 16:22:40 -04:00
Tom Alexander
a7b9eb9db4 Lifetime issue. 2023-09-03 12:58:46 -04:00
Tom Alexander
d262833f9b Fixing more errors. 2023-09-03 12:52:09 -04:00
Tom Alexander
0d438a8e0f Fixing more errors. 2023-09-03 12:45:12 -04:00
Tom Alexander
0b009511ff Fixing more errors. 2023-09-03 12:28:45 -04:00
Tom Alexander
3bdb24ad88 Fixing more errors. 2023-09-03 12:23:18 -04:00
Tom Alexander
fdf35ba23c Fixing more errors. 2023-09-03 12:07:51 -04:00
Tom Alexander
cd69e08516 Fixing more errors. 2023-09-03 11:05:34 -04:00
Tom Alexander
b54c6d366c Fixing more errors. 2023-09-03 00:27:50 -04:00
Tom Alexander
15e8d1ab77 Implement check_exit_matcher. 2023-09-03 00:05:47 -04:00
Tom Alexander
8502a8830d Fixing some errors. 2023-09-02 23:16:02 -04:00
Tom Alexander
74a6101de7 Update RefContext to three lifetimes. 2023-09-02 22:45:46 -04:00
Tom Alexander
ba57eb16fd Fix using references for context elements. 2023-09-02 22:44:21 -04:00
Tom Alexander
c309d14776 Fix a reference to RefContext. 2023-09-02 20:52:02 -04:00
Tom Alexander
0d728510d7 Implement iterator for context. 2023-09-02 20:46:17 -04:00
Tom Alexander
22e9bc991f Fixing up compiler errors. 2023-09-02 19:28:33 -04:00
Tom Alexander
564104f1e8 Switch to RefContext. 2023-09-02 19:16:44 -04:00
Tom Alexander
12ad3b09f0 Fixing imports. 2023-09-02 19:15:47 -04:00
Tom Alexander
eabffe5ecc Move over the rest of the types. 2023-09-02 19:08:01 -04:00
Tom Alexander
b47029fdbb Starting to separate the context and parsed tokens into their own modules. 2023-09-02 18:46:45 -04:00
Tom Alexander
25b8c80d4e Add default constructors. 2023-09-02 18:40:01 -04:00
Tom Alexander
54825538e4 fixup 2023-09-02 18:21:43 -04:00
Tom Alexander
66d10a7a1b Started switching over to a stack-based context tree with global settings.
This change should hopefully allow for matchers to have captured borrowed values, it should eliminate the use of heap-allocated reference counting on the context nodes, and it adds in a global settings struct for passing around values that do not change during parsing.
2023-09-02 18:20:10 -04:00
Tom Alexander
acf1205e75 Merge branch 'file_based_diff'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-09-02 17:28:31 -04:00
Tom Alexander
2cd2f7570c Update scripts to handle passing files as parameters. 2023-09-02 17:28:22 -04:00
Tom Alexander
f16a554154 Handle org-mode documents passed as args.
We were running into issues where the documents grew too large for being passed as a string to emacs, and we need to handle #+setupfile so we need to start handling org-mode documents as files and not just as anonymous streams of text.

The anonymous stream of text handling will remain because the automated tests use it.
2023-09-02 17:28:22 -04:00
Tom Alexander
a40a504f94 Switch to using read-only root in docker containers.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 21:23:51 -04:00
Tom Alexander
80d77ff5d6 Fix handling of unicode in compare tests.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 20:20:00 -04:00
Tom Alexander
ee92049e5d Merge branch 'special_block'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 19:19:29 -04:00
Tom Alexander
510985e97c Fix greater blocks by preventing nesting even if they are not the immediate parent. 2023-08-31 19:17:46 -04:00
Tom Alexander
949d0989f4 Add a test showing the current parser is broken with deeply nested greater blocks. 2023-08-31 19:15:28 -04:00
Tom Alexander
2a4d22bdd4 Add test for nesting of greater blocks.
This shows that greater blocks of different types can be directly nested even if they are both special blocks (as long as they are different special blocks).
2023-08-31 19:09:38 -04:00
Tom Alexander
7a903acedc Support special blocks in compare. 2023-08-31 19:04:44 -04:00
Tom Alexander
5171326d63 Create a test for special blocks. 2023-08-31 19:02:18 -04:00
Tom Alexander
67f79aeb51 Remove context from detect plain list.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 18:53:28 -04:00
Tom Alexander
b2383d9f93 Fix footnote definition performance.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
We were re-parsing each footnote definition in the footnote definition exit matcher which causes their contents to get re-parsed. This compounds with long lists of footnote definitions.
2023-08-31 18:47:23 -04:00
Tom Alexander
9e2a323f6f Merge branch 'trailing_whitespace_at_end_of_file'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 17:29:52 -04:00
Tom Alexander
0fcb3f73f9 Move handling of contentless item to handle contentless description item. 2023-08-31 17:25:42 -04:00
Tom Alexander
bfc9e7f58d When re-parsing last item in list, only disable white space consumption for last element in item. 2023-08-31 17:08:21 -04:00
Tom Alexander
b5f0521b56 Only consume trailing whitespace when not exiting for all objects. 2023-08-31 15:45:31 -04:00
Tom Alexander
2048d8f0b6 Support comments at the end of the line in diary sexp.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 15:31:54 -04:00
Tom Alexander
466716881e Fix macros consuming whitespace even when the exit matcher is calling for an exit.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 15:11:29 -04:00
Tom Alexander
eb9c582fa5 '>' is allowed as a post character in text markup.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:57:15 -04:00
Tom Alexander
214e895d85 Support backslash at the end of a macro. 2023-08-29 23:42:16 -04:00
124 changed files with 4318 additions and 2385 deletions

View File

@@ -0,0 +1,203 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: rust-foreign-document-test
spec:
pipelineSpec:
timeouts:
pipeline: "2h0m0s"
tasks: "1h0m40s"
finally: "0h30m0s"
params:
- name: image-name
description: The name for the built image
type: string
- name: path-to-image-context
description: The path to the build context
type: string
- name: path-to-dockerfile
description: The path to the Dockerfile
type: string
tasks:
- name: do-stuff
taskSpec:
metadata: {}
stepTemplate:
image: alpine:3.18
name: ""
resources:
requests:
cpu: 10m
memory: 600Mi
workingDir: /workspace/source
steps:
- image: alpine:3.18
name: do-stuff-step
script: |
#!/usr/bin/env sh
echo "hello world"
- name: report-pending
taskRef:
name: gitea-set-status
runAfter:
- fetch-repository
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has started"
- name: STATE
value: pending
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: fetch-repository
taskRef:
name: git-clone
workspaces:
- name: output
workspace: git-source
params:
- name: url
value: $(params.REPO_URL)
- name: revision
value: $(params.PULL_BASE_SHA)
- name: deleteExisting
value: "true"
- name: build-image
taskRef:
name: kaniko
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: CONTEXT
value: $(params.path-to-image-context)
- name: DOCKERFILE
value: $(params.path-to-dockerfile)
- name: BUILDER_IMAGE
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- --target=foreign-document-test
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
- --use-new-run # Should result in a speed-up
- --reproducible # To remove timestamps so layer caching works.
- --snapshot-mode=redo
- --skip-unused-stages=true
- --registry-mirror=dockerhub.dockerhub.svc.cluster.local
workspaces:
- name: source
workspace: git-source
- name: dockerconfig
workspace: docker-credentials
runAfter:
- fetch-repository
- name: run-image
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- build-image
params:
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally:
- name: report-success
when:
- input: "$(tasks.status)"
operator: in
values: ["Succeeded", "Completed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has succeeded"
- name: STATE
value: success
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: report-failure
when:
- input: "$(tasks.status)"
operator: in
values: ["Failed"]
taskRef:
name: gitea-set-status
params:
- name: CONTEXT
value: "$(params.JOB_NAME)"
- name: REPO_FULL_NAME
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
- name: GITEA_HOST_URL
value: code.fizz.buzz
- name: SHA
value: "$(tasks.fetch-repository.results.commit)"
- name: DESCRIPTION
value: "Build $(params.JOB_NAME) has failed"
- name: STATE
value: failure
- name: TARGET_URL
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
- name: cargo-cache-autoclean
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
params:
- name: command
value: [cargo, cache, --autoclean]
- name: args
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
workspaces:
- name: git-source
- name: docker-credentials
- name: cargo-cache
workspaces:
- name: git-source
volumeClaimTemplate:
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
subPath: rust-source
- name: cargo-cache
persistentVolumeClaim:
claimName: organic-cargo-cache-test-foreign-document
- name: docker-credentials
secret:
secretName: harbor-plain
serviceAccountName: build-bot
params:
- name: image-name
value: "harbor.fizz.buzz/private/organic-test-foreign-document"
- name: path-to-image-context
value: docker/organic_test/
- name: path-to-dockerfile
value: docker/organic_test/Dockerfile

View File

@@ -83,6 +83,7 @@ spec:
value: "gcr.io/kaniko-project/executor:v1.12.1" value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS - name: EXTRA_ARGS
value: value:
- --target=tester
- --cache=true - --cache=true
- --cache-copy-layers - --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache - --cache-repo=harbor.fizz.buzz/kanikocache/cache

View File

@@ -16,6 +16,13 @@ spec:
skip_branches: skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing. # We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$" - "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
- name: rust-foreign-document-test
source: "pipeline-foreign-document-test.yaml"
# Override https-based url from lighthouse events.
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
skip_branches:
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
- name: rust-build - name: rust-build
source: "pipeline-rust-build.yaml" source: "pipeline-rust-build.yaml"
# Override https-based url from lighthouse events. # Override https-based url from lighthouse events.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.4" version = "0.1.7"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser." description = "An org-mode parser."
edition = "2021" edition = "2021"
@@ -21,9 +21,15 @@ name = "organic"
path = "src/lib.rs" path = "src/lib.rs"
[[bin]] [[bin]]
# This bin exists for development purposes only. The real target of this crate is the library. # This bin exists for development purposes only. The real target of this crate is the library.
name = "compare" name = "parse"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the library.
name = "compare"
path = "src/bin_compare.rs"
required-features = ["compare"]
[dependencies] [dependencies]
nom = "7.1.1" nom = "7.1.1"

View File

@@ -40,7 +40,11 @@ test:
.PHONY: dockertest .PHONY: dockertest
dockertest: dockertest:
> $(MAKE) -C docker/organic_test > $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --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) > 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: foreign_document_test
foreign_document_test:
> $(MAKE) -C docker/organic_test run_foreign_document_test
.PHONY: dockerclean .PHONY: dockerclean
dockerclean: dockerclean:
@@ -60,7 +64,7 @@ jaeger:
# 4317 for OTLP gRPC, 4318 for OTLP HTTP. We currently use gRPC but I forward both ports regardless. # 4317 for OTLP gRPC, 4318 for OTLP HTTP. We currently use gRPC but I forward both ports regardless.
# #
# These flags didn't help even though they seem like they would: --collector.queue-size=20000 --collector.num-workers=100 # These flags didn't help even though they seem like they would: --collector.queue-size=20000 --collector.num-workers=100
> docker run -d --rm --name organicdocker -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=20000000 --collector.otlp.grpc.max-message-size=20000000 > docker run -d --rm --name organicdocker --read-only -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=20000000 --collector.otlp.grpc.max-message-size=20000000
.PHONY: jaegerweb .PHONY: jaegerweb
jaegerweb: jaegerweb:

View File

@@ -71,10 +71,6 @@ fn write_header(test_file: &mut File) {
test_file, test_file,
r#" r#"
#[feature(exit_status_error)] #[feature(exit_status_error)]
use organic::compare_document;
use organic::parser::document;
use organic::emacs_parse_org_document;
use organic::parser::sexp::sexp_with_padding;
"# "#
) )
@@ -86,8 +82,6 @@ fn is_expect_fail(name: &str) -> Option<&str> {
match name { match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."), "autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."), "autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
"autogen_unicode_hearts" => Some("Unicode is coming out of emacs strange."),
_ => None, _ => None,
} }
} }

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build .PHONY: build
build: build:
docker build -t $(IMAGE_NAME) -f Dockerfile ../../ docker build -t $(IMAGE_NAME) -f Dockerfile .
.PHONY: push .PHONY: push
push: push:
@@ -29,8 +29,8 @@ endif
# NOTE: This target will write to folders underneath the git-root # NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME) docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build .PHONY: build
build: build:
docker build -t $(IMAGE_NAME) -f Dockerfile ../../ docker build -t $(IMAGE_NAME) -f Dockerfile .
.PHONY: push .PHONY: push
push: push:
@@ -30,8 +30,8 @@ endif
# NOTE: This target will write to folders underneath the git-root # NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME) docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)

View File

@@ -14,7 +14,7 @@ RUN make DESTDIR="/root/dist" install
FROM build AS build-org-mode FROM build AS build-org-mode
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1 ARG ORG_VERSION=163bafb43dcc2bc94a2c7ccaa77d3d1dd488f1af
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /
RUN mkdir /root/dist RUN mkdir /root/dist
# Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want. # Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want.
@@ -25,10 +25,77 @@ RUN make compile
RUN make DESTDIR="/root/dist" install RUN make DESTDIR="/root/dist" install
FROM rustlang/rust:nightly-alpine3.17 FROM rustlang/rust:nightly-alpine3.17 AS tester
ENV LANG=en_US.UTF-8
RUN apk add --no-cache musl-dev ncurses gnutls RUN apk add --no-cache musl-dev ncurses gnutls
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /
COPY --from=build-org-mode /root/dist/ / COPY --from=build-org-mode /root/dist/ /
ENTRYPOINT ["cargo", "test"] ENTRYPOINT ["cargo", "test"]
FROM build as foreign-document-gather
ARG HOWARD_ABRAMS_DOT_FILES_VERSION=1b54fe75d74670dc7bcbb6b01ea560c45528c628
ARG HOWARD_ABRAMS_DOT_FILES_PATH=/foreign_documents/howardabrams/dot-files
ARG HOWARD_ABRAMS_DOT_FILES_REPO=https://github.com/howardabrams/dot-files.git
RUN mkdir /foreign_documents
RUN mkdir -p $HOWARD_ABRAMS_DOT_FILES_PATH && git -C $HOWARD_ABRAMS_DOT_FILES_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DOT_FILES_PATH remote add origin $HOWARD_ABRAMS_DOT_FILES_REPO && git -C $HOWARD_ABRAMS_DOT_FILES_PATH fetch origin $HOWARD_ABRAMS_DOT_FILES_VERSION && git -C $HOWARD_ABRAMS_DOT_FILES_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_HAMACS_VERSION=da51188cc195d41882175d412fe40a8bc5730c5c
ARG HOWARD_ABRAMS_HAMACS_PATH=/foreign_documents/howardabrams/hamacs
ARG HOWARD_ABRAMS_HAMACS_REPO=https://github.com/howardabrams/hamacs.git
RUN mkdir -p $HOWARD_ABRAMS_HAMACS_PATH && git -C $HOWARD_ABRAMS_HAMACS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_HAMACS_PATH remote add origin $HOWARD_ABRAMS_HAMACS_REPO && git -C $HOWARD_ABRAMS_HAMACS_PATH fetch origin $HOWARD_ABRAMS_HAMACS_VERSION && git -C $HOWARD_ABRAMS_HAMACS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_DEMO_IT_VERSION=e399fd7ceb73caeae7cb50b247359bafcaee2a3f
ARG HOWARD_ABRAMS_DEMO_IT_PATH=/foreign_documents/howardabrams/demo-it
ARG HOWARD_ABRAMS_DEMO_IT_REPO=https://github.com/howardabrams/demo-it.git
RUN mkdir -p $HOWARD_ABRAMS_DEMO_IT_PATH && git -C $HOWARD_ABRAMS_DEMO_IT_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DEMO_IT_PATH remote add origin $HOWARD_ABRAMS_DEMO_IT_REPO && git -C $HOWARD_ABRAMS_DEMO_IT_PATH fetch origin $HOWARD_ABRAMS_DEMO_IT_VERSION && git -C $HOWARD_ABRAMS_DEMO_IT_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_MAGIT_DEMO_VERSION=59e82f6bc7c18f550478d86a8f680c3f2da66985
ARG HOWARD_ABRAMS_MAGIT_DEMO_PATH=/foreign_documents/howardabrams/magit-demo
ARG HOWARD_ABRAMS_MAGIT_DEMO_REPO=https://github.com/howardabrams/magit-demo.git
RUN mkdir -p $HOWARD_ABRAMS_MAGIT_DEMO_PATH && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH remote add origin $HOWARD_ABRAMS_MAGIT_DEMO_REPO && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH fetch origin $HOWARD_ABRAMS_MAGIT_DEMO_VERSION && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION=bfb7bd640fdf0ce3def21f9fc591ed35d776b26d
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH=/foreign_documents/howardabrams/pdx-emacs-hackers
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO=https://github.com/howardabrams/pdx-emacs-hackers.git
RUN mkdir -p $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH remote add origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH fetch origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION=50de13068722b9e3878f8598b749b7ccd14e7f8e
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_PATH=/foreign_documents/howardabrams/flora-simulator
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_REPO=https://github.com/howardabrams/flora-simulator.git
RUN mkdir -p $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH remote add origin $HOWARD_ABRAMS_FLORA_SIMULATOR_REPO && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH fetch origin $HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION=2d7a5e41001a1adf7ec24aeb6acc8525a72d7892
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH=/foreign_documents/howardabrams/literate-devops-demo
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO=https://github.com/howardabrams/literate-devops-demo.git
RUN mkdir -p $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH remote add origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH fetch origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION=b651c7f8b47b2710e99fce9652980902bbc1c6c9
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH=/foreign_documents/howardabrams/clojure-yesql-xp
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO=https://github.com/howardabrams/clojure-yesql-xp.git
RUN mkdir -p $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH remote add origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH fetch origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_VEEP_VERSION=e37fcf63a5c4a526255735ee34955528b3b280ae
ARG HOWARD_ABRAMS_VEEP_PATH=/foreign_documents/howardabrams/veep
ARG HOWARD_ABRAMS_VEEP_REPO=https://github.com/howardabrams/veep.git
RUN mkdir -p $HOWARD_ABRAMS_VEEP_PATH && git -C $HOWARD_ABRAMS_VEEP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_VEEP_PATH remote add origin $HOWARD_ABRAMS_VEEP_REPO && git -C $HOWARD_ABRAMS_VEEP_PATH fetch origin $HOWARD_ABRAMS_VEEP_VERSION && git -C $HOWARD_ABRAMS_VEEP_PATH checkout FETCH_HEAD
ARG DOOMEMACS_VERSION=42d5fd83504f8aa80f3248036006fbcd49222943
ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
FROM tester as foreign-document-test
RUN apk add --no-cache bash coreutils
RUN mkdir /foreign_documents
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
COPY foreign_document_test_entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -6,7 +6,11 @@ all: build push
.PHONY: build .PHONY: build
build: build:
docker build -t $(IMAGE_NAME) -f Dockerfile ../../ docker build -t $(IMAGE_NAME) -f Dockerfile --target tester .
.PHONY: build_foreign_document_test
build_foreign_document_test:
docker build -t $(IMAGE_NAME)-foreign-document -f Dockerfile --target foreign-document-test .
.PHONY: push .PHONY: push
push: push:
@@ -29,8 +33,12 @@ endif
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME) --no-default-features --features compare --no-fail-fast --lib --test test_loader docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME) --no-default-features --features compare --no-fail-fast --lib --test test_loader
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
.PHONY: run_foreign_document_test
run_foreign_document_test: build_foreign_document_test
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)-foreign-document

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env bash
#
# Run the Organic compare script against a series of documents sourced from exterior places.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
function log {
(>&2 echo "${@}")
}
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function main {
cargo build --no-default-features --features compare --profile release-lto
if [ "${CARGO_TARGET_DIR:-}" = "" ]; then
CARGO_TARGET_DIR=$(realpath target/)
fi
PARSE="${CARGO_TARGET_DIR}/release-lto/compare"
local all_status=0
set +e
(run_compare_function "org-mode" compare_all_org_document "/foreign_documents/org-mode")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "howard_abrams" compare_howard_abrams)
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
if [ "$all_status" -ne 0 ]; then
echo "$(red_text "Some tests failed.")"
else
echo "$(green_text "All tests passed.")"
fi
return "$all_status"
}
function green_text {
(IFS=' '; printf '\x1b[38;2;0;255;0m%s\x1b[0m' "${*}")
}
function red_text {
(IFS=' '; printf '\x1b[38;2;255;0;0m%s\x1b[0m' "${*}")
}
function yellow_text {
(IFS=' '; printf '\x1b[38;2;255;255;0m%s\x1b[0m' "${*}")
}
function indent {
local depth="$1"
local scaled_depth=$((depth * 2))
shift 1
local prefix=$(printf -- "%${scaled_depth}s")
while read l; do
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
done
}
function run_compare_function {
local name="$1"
local stdoutput
shift 1
set +e
stdoutput=$("${@}")
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$(green_text "GOOD") $name"
indent 1 <<<"$stdoutput"
else
echo "$(red_text "FAIL") $name"
indent 1 <<<"$stdoutput"
return 1
fi
}
function compare_all_org_document {
local root_dir="$1"
local target_document
local all_status=0
while read target_document; do
local relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
set +e
(run_compare "$relative_path" "$target_document")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
done<<<$(find "$root_dir" -type f -iname '*.org')
return "$all_status"
}
function run_compare {
local name="$1"
local target_document="$2"
set +e
($PARSE "$target_document" &> /dev/null)
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$(green_text "GOOD") $name"
else
echo "$(red_text "FAIL") $name"
return 1
fi
}
function compare_howard_abrams {
local all_status=0
set +e
(run_compare_function "dot-files" compare_all_org_document "/foreign_documents/howardabrams/dot-files")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "hamacs" compare_all_org_document "/foreign_documents/howardabrams/hamacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "demo-it" compare_all_org_document "/foreign_documents/howardabrams/demo-it")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "magit-demo" compare_all_org_document "/foreign_documents/howardabrams/magit-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "pdx-emacs-hackers" compare_all_org_document "/foreign_documents/howardabrams/pdx-emacs-hackers")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "flora-simulator" compare_all_org_document "/foreign_documents/howardabrams/flora-simulator")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "literate-devops-demo" compare_all_org_document "/foreign_documents/howardabrams/literate-devops-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "clojure-yesql-xp" compare_all_org_document "/foreign_documents/howardabrams/clojure-yesql-xp")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "veep" compare_all_org_document "/foreign_documents/howardabrams/veep")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
return "$all_status"
}
main "${@}"

View File

@@ -0,0 +1 @@
This folder contains org-mode documents that get automatically included as tests using build.rs.

View File

@@ -0,0 +1,8 @@
* Footnotes
[fn:1]
#+BEGIN_EXAMPLE
baz
#+END_EXAMPLE

View File

@@ -0,0 +1,18 @@
#+begin_defun
foo
#+begin_lorem
,#+begin_center
bar
,#+end_center
ipsum
#+end_lorem
baz
#+end_defun
#+begin_center
#+begin_quote
#+begin_center
lorem
#+end_center
#+end_quote
#+end_center

View File

@@ -0,0 +1,12 @@
#+begin_defun
foo
#+begin_lorem
ipsum
#+end_lorem
bar
#+begin_center
#+begin_quote
baz
#+end_quote
#+end_center
#+end_defun

View File

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

View File

@@ -0,0 +1,2 @@
3. [@3] foo
4. bar

View File

@@ -0,0 +1,2 @@
- {{{foo(bar)}}} :: baz
- =foo= :: bar

View File

@@ -0,0 +1,3 @@
- foo :: bar
- foo :: bar
- foo :: bar

View File

@@ -0,0 +1,2 @@
- =foo :: bar= :: baz
- lorem :: ipsum :: dolar

View File

@@ -1,3 +1,5 @@
1. 1.
2. 2.
3. 3.
* headline

View File

@@ -0,0 +1,2 @@
- foo

View File

@@ -0,0 +1,6 @@
* Overwrite
:PROPERTIES:
:header-args: :var foo="lorem"
:header-args:emacs-lisp: :var bar="ipsum"
:header-args:emacs-lisp+: :results silent :var baz=7
:END:

View File

@@ -0,0 +1,6 @@
src_elisp{(bar)}
*src_elisp{(bar)}*
| foo *bar* |
| foo src_elisp{(bar)} |
| foo *src_elisp{(bar)}* |

View File

@@ -0,0 +1,8 @@
| Name | Price | Quantity | Total |
|------+-------+----------+-------|
| foo | 7 | 4 | 28 |
| bar | 3.5 | 3 | 10.5 |
|------+-------+----------+-------|
| | | 7 | 38.5 |
#+tblfm: $4=$2*$3::@>$4=vsum(@2..@-1)
#+tblfm: @>$3=vsum(@2..@-1)

View File

@@ -0,0 +1,6 @@
%%(foo
)
%%(bar ; baz
lorem

View File

@@ -0,0 +1 @@
%%(foo bar) ; baz

View File

@@ -0,0 +1,7 @@
# This test is to prove that the parser works with affiliated keywords that have both a shorter and longer version.
#+results:
#+result:
#+begin_latex
\foo
#+end_latex

View File

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

View File

@@ -0,0 +1 @@
#+title:foo:bar: baz: lorem: ipsum

View File

@@ -0,0 +1,2 @@
#+begin_src
#+end_src

View File

@@ -0,0 +1,4 @@
# There are trailing spaces after the begin and end src lines
#+begin_src
echo "this is a source block."
#+end_src

View File

@@ -0,0 +1,3 @@
*[fn:: /abcdef[fn::ghijklmnopqrstuvw]xyz/ r]*
*[fn:: /abcdef[fn::ghijk *lmnopq* rstuvw]xyz/ r]*

View File

@@ -0,0 +1 @@
mailto:foo@bar.baz .

View File

@@ -0,0 +1,4 @@
[/]
[/2]
[3/]
[%]

View File

@@ -0,0 +1,4 @@
foo ==>bar=.
# This uses a zero-width space to escape the equals signs to make the verbatim not end.
=lorem == ipsum=

View File

@@ -0,0 +1,2 @@
* TODO [#A] COMMENT foo bar
baz

View File

@@ -0,0 +1,4 @@
* DONE foo
DEADLINE: <2023-09-08 Fri>
* DONE bar

View File

@@ -0,0 +1 @@
* [0/4] foo

View File

@@ -4,10 +4,10 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
RUSTFLAGS="-C opt-level=0" cargo build --no-default-features
valgrind --tool=callgrind --callgrind-out-file=callgrind.out target/debug/compare (cd "$DIR/../" && RUSTFLAGS="-C opt-level=0" cargo build --no-default-features)
valgrind --tool=callgrind --callgrind-out-file="$DIR/../callgrind.out" "$DIR/../target/debug/parse" "${@}"
echo "You probably want to run:" echo "You probably want to run:"
echo "callgrind_annotate --auto=yes callgrind.out" echo "callgrind_annotate --auto=yes '$DIR/../callgrind.out'"

View File

@@ -6,8 +6,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="perf"} : ${PROFILE:="perf"}
cd "$DIR/../"
function main { function main {
local additional_flags=() local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
@@ -15,12 +13,12 @@ function main {
else else
additional_flags+=(--profile "$PROFILE") additional_flags+=(--profile "$PROFILE")
fi fi
cargo build --no-default-features "${additional_flags[@]}" (cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare perf record --freq=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
# Convert to a format firefox will read # Convert to a format firefox will read
# flags to consider --show-info # flags to consider --show-info
perf script -F +pid --input perf.data > perf.firefox perf script -F +pid --input "$DIR/../perf.data" > "$DIR/../perf.firefox"
echo "You probably want to go to https://profiler.firefox.com/" echo "You probably want to go to https://profiler.firefox.com/"
echo "Either that or run hotspot" echo "Either that or run hotspot"

View File

@@ -9,14 +9,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking : ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
: ${NO_COLOR:=""} # Set to anything to disable color output : ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make) MAKE=$(command -v gmake || command -v make)
function main { function main {
build_container build_container
launch_container launch_container "${@}"
} }
function build_container { function build_container {
@@ -40,7 +38,8 @@ function launch_container {
if [ "$SHELL" != "YES" ]; then if [ "$SHELL" != "YES" ]; then
local features_joined=$(IFS=","; echo "${features[*]}") local features_joined=$(IFS=","; echo "${features[*]}")
additional_args+=(cargo run --no-default-features --features "$features_joined") additional_args+=(cargo run --bin compare --no-default-features --features "$features_joined")
additional_flags+=(--read-only)
else else
additional_args+=(/bin/sh) additional_args+=(/bin/sh)
additional_flags+=(-t) additional_flags+=(-t)
@@ -50,7 +49,17 @@ function launch_container {
additional_flags+=(--env RUST_BACKTRACE=full) additional_flags+=(--env RUST_BACKTRACE=full)
fi fi
docker run "${additional_flags[@]}" --init --rm -i -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" if [ $# -gt 0 ]; then
# If we passed in args, we need to forward them along
for path in "${@}"; do
local full_path=$($REALPATH "$path")
local containing_folder=$(dirname "$full_path")
local file_name=$(basename "$full_path")
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" -- "/input/$file_name"
done
else
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
fi
} }
main "${@}" main "${@}"

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
#
# Bisect parsing a file at various line cut-off points to see which line causes the parse to differ from emacs.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
############## Setup #########################
function cleanup {
for f in "${folders[@]}"; do
log "Deleting $f"
rm -rf "$f"
done
}
folders=()
for sig in EXIT INT QUIT HUP TERM; do
trap "set +e; cleanup" "$sig"
done
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
log "Is is recommended that the output of \`mktemp -d -t 'compare_bisect.XXXXXXXX'\` is inside a tmpfs filesystem since this script will make many writes to these folders."
local target_full_path=$($REALPATH "$1")
SOURCE_FOLDER=$(dirname "$target_full_path")
TARGET_DOCUMENT=$(basename "$target_full_path")
local good=0
local bad=$(wc -l "$SOURCE_FOLDER/$TARGET_DOCUMENT" | awk '{print $1}')
set +e
run_parse "$bad" &> /dev/null
local status=$?
set -e
if [ $status -eq 0 ]; then
log "Entire file passes."
exit 0
fi
while [[ "$((bad - good))" -gt 1 ]]; do
local next_line=$((((bad - good) / 2) + good))
log "Testing line $next_line"
set +e
run_parse "$next_line" &> /dev/null
local status=$?
set -e
if [ $status -eq 0 ]; then
good="$next_line"
log "Line $next_line good"
else
bad="$next_line"
log "Line $next_line bad"
fi
done
echo "Bad line: $bad"
}
function setup_temp_dir {
local temp_dir=$(mktemp -d -t 'compare_bisect.XXXXXXXX')
cp -r "$SOURCE_FOLDER/"* "$temp_dir/"
echo "$temp_dir"
}
function run_parse {
local lines="$1"
local temp_dir=$(setup_temp_dir)
folders+=("$temp_dir")
cat "$SOURCE_FOLDER/$TARGET_DOCUMENT" | head -n "$lines" > "$temp_dir/$TARGET_DOCUMENT"
"${DIR}/run_docker_compare.bash" "$temp_dir/$TARGET_DOCUMENT"
local status=$?
rm -rf "$temp_dir"
# TODO: Remove temp_dir from folders
return "$status"
}
main "${@}"

View File

@@ -6,7 +6,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${NO_COLOR:=""} # Set to anything to disable color output : ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make) MAKE=$(command -v gmake || command -v make)
@@ -33,7 +32,7 @@ function get_test_names {
local test_file_full_path=$($REALPATH "$test_file") local test_file_full_path=$($REALPATH "$test_file")
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path") local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
local without_extension="${relative_to_samples%.org}" local without_extension="${relative_to_samples%.org}"
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]' echo "autogen_${without_extension//\//_}" | tr '[:upper:]' '[:lower:]'
else else
echo "$test_file" | tr '[:upper:]' '[:lower:]' echo "$test_file" | tr '[:upper:]' '[:lower:]'
fi fi
@@ -56,7 +55,7 @@ cargo test --no-default-features --features compare --no-fail-fast --lib --test
EOF EOF
) )
docker run "${additional_flags[@]}" --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script" docker run "${additional_flags[@]}" --init --rm --read-only --mount type=tmpfs,destination=/tmp -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
} }

View File

@@ -4,7 +4,6 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
function main { function main {
@@ -12,7 +11,7 @@ function main {
local test local test
while read test; do while read test; do
cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output (cd "$DIR/../" && cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output)
done<<<"$test_names" done<<<"$test_names"
} }
@@ -25,7 +24,7 @@ function get_test_names {
local test_file_full_path=$($REALPATH "$test_file") local test_file_full_path=$($REALPATH "$test_file")
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path") local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
local without_extension="${relative_to_samples%.org}" local without_extension="${relative_to_samples%.org}"
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]' echo "${without_extension//\//_}" | tr '[:upper:]' '[:lower:]'
else else
echo "$test_file" | tr '[:upper:]' '[:lower:]' echo "$test_file" | tr '[:upper:]' '[:lower:]'
fi fi

View File

@@ -7,8 +7,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="release-lto"} : ${PROFILE:="release-lto"}
cd "$DIR/../"
function main { function main {
local additional_flags=() local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
@@ -16,8 +14,8 @@ function main {
else else
additional_flags+=(--profile "$PROFILE") additional_flags+=(--profile "$PROFILE")
fi fi
cargo build --no-default-features "${additional_flags[@]}" (cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
time ./target/${PROFILE}/compare time "$DIR/../target/${PROFILE}/parse" "${@}"
} }
main "${@}" main "${@}"

52
src/bin_compare.rs Normal file
View File

@@ -0,0 +1,52 @@
#![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
use std::io::Read;
use organic::compare::run_anonymous_compare;
use organic::compare::run_compare_on_file;
#[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry;
#[cfg(feature = "tracing")]
use crate::init_tracing::shutdown_telemetry;
#[cfg(feature = "tracing")]
mod init_tracing;
#[cfg(not(feature = "tracing"))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
main_body()
}
#[cfg(feature = "tracing")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
init_telemetry()?;
let main_body_result = main_body();
shutdown_telemetry()?;
main_body_result
});
result
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args().skip(1);
if args.is_empty() {
let org_contents = read_stdin_to_string()?;
run_anonymous_compare(org_contents)
} else {
for arg in args {
run_compare_on_file(arg)?
}
Ok(())
}
}
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
let mut stdin_contents = String::new();
std::io::stdin()
.lock()
.read_to_string(&mut stdin_contents)?;
Ok(stdin_contents)
}

73
src/compare/compare.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::path::Path;
use crate::compare::diff::compare_document;
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;
use crate::compare::parse::get_org_mode_version;
use crate::compare::sexp::sexp;
use crate::parser::parse;
use crate::parser::parse_with_settings;
use crate::GlobalSettings;
use crate::LocalFileAccessInterface;
pub fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P,
) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = org_contents.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let rust_parsed = parse(org_contents)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
let org_contents = org_contents.as_str();
let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
let org_sexp = emacs_parse_file_org_document(org_path)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}

View File

@@ -1,68 +1,72 @@
use std::collections::BTreeSet;
use std::collections::HashSet; use std::collections::HashSet;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::assert_bounds; use super::util::assert_bounds;
use super::util::assert_name; use super::util::assert_name;
use super::util::get_property; use super::util::get_property;
use crate::parser::sexp::unquote; use crate::types::AngleLink;
use crate::parser::sexp::Token; use crate::types::Bold;
use crate::parser::AngleLink; use crate::types::Citation;
use crate::parser::Bold; use crate::types::CitationReference;
use crate::parser::Citation; use crate::types::Clock;
use crate::parser::CitationReference; use crate::types::Code;
use crate::parser::Clock; use crate::types::Comment;
use crate::parser::Code; use crate::types::CommentBlock;
use crate::parser::Comment; use crate::types::DiarySexp;
use crate::parser::CommentBlock; use crate::types::Document;
use crate::parser::DiarySexp; use crate::types::DocumentElement;
use crate::parser::Document; use crate::types::Drawer;
use crate::parser::DocumentElement; use crate::types::DynamicBlock;
use crate::parser::Drawer; use crate::types::Element;
use crate::parser::DynamicBlock; use crate::types::Entity;
use crate::parser::Element; use crate::types::ExampleBlock;
use crate::parser::Entity; use crate::types::ExportBlock;
use crate::parser::ExampleBlock; use crate::types::ExportSnippet;
use crate::parser::ExportBlock; use crate::types::FixedWidthArea;
use crate::parser::ExportSnippet; use crate::types::FootnoteDefinition;
use crate::parser::FixedWidthArea; use crate::types::FootnoteReference;
use crate::parser::FootnoteDefinition; use crate::types::GreaterBlock;
use crate::parser::FootnoteReference; use crate::types::Heading;
use crate::parser::GreaterBlock; use crate::types::HorizontalRule;
use crate::parser::Heading; use crate::types::InlineBabelCall;
use crate::parser::HorizontalRule; use crate::types::InlineSourceBlock;
use crate::parser::InlineBabelCall; use crate::types::Italic;
use crate::parser::InlineSourceBlock; use crate::types::Keyword;
use crate::parser::Italic; use crate::types::LatexEnvironment;
use crate::parser::Keyword; use crate::types::LatexFragment;
use crate::parser::LatexEnvironment; use crate::types::LineBreak;
use crate::parser::LatexFragment; use crate::types::NodeProperty;
use crate::parser::LineBreak; use crate::types::Object;
use crate::parser::Object; use crate::types::OrgMacro;
use crate::parser::OrgMacro; use crate::types::Paragraph;
use crate::parser::Paragraph; use crate::types::PlainLink;
use crate::parser::PlainLink; use crate::types::PlainList;
use crate::parser::PlainList; use crate::types::PlainListItem;
use crate::parser::PlainListItem; use crate::types::PlainText;
use crate::parser::PlainText; use crate::types::Planning;
use crate::parser::Planning; use crate::types::PriorityCookie;
use crate::parser::PropertyDrawer; use crate::types::PropertyDrawer;
use crate::parser::RadioLink; use crate::types::RadioLink;
use crate::parser::RadioTarget; use crate::types::RadioTarget;
use crate::parser::RegularLink; use crate::types::RegularLink;
use crate::parser::Section; use crate::types::Section;
use crate::parser::Source; use crate::types::Source;
use crate::parser::SrcBlock; use crate::types::SrcBlock;
use crate::parser::StatisticsCookie; use crate::types::StatisticsCookie;
use crate::parser::StrikeThrough; use crate::types::StrikeThrough;
use crate::parser::Subscript; use crate::types::Subscript;
use crate::parser::Superscript; use crate::types::Superscript;
use crate::parser::Table; use crate::types::Table;
use crate::parser::TableCell; use crate::types::TableCell;
use crate::parser::TableRow; use crate::types::TableRow;
use crate::parser::Target; use crate::types::Target;
use crate::parser::Timestamp; use crate::types::Timestamp;
use crate::parser::Underline; use crate::types::TodoKeywordType;
use crate::parser::Verbatim; use crate::types::Underline;
use crate::parser::VerseBlock; use crate::types::Verbatim;
use crate::types::VerseBlock;
#[derive(Debug)] #[derive(Debug)]
pub enum DiffEntry<'s> { pub enum DiffEntry<'s> {
@@ -82,7 +86,7 @@ pub struct DiffResult<'s> {
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DiffStatus { enum DiffStatus {
Good, Good,
Bad, Bad,
} }
@@ -305,6 +309,7 @@ fn compare_element<'s>(
Element::FixedWidthArea(obj) => compare_fixed_width_area(source, emacs, obj), Element::FixedWidthArea(obj) => compare_fixed_width_area(source, emacs, obj),
Element::HorizontalRule(obj) => compare_horizontal_rule(source, emacs, obj), Element::HorizontalRule(obj) => compare_horizontal_rule(source, emacs, obj),
Element::Keyword(obj) => compare_keyword(source, emacs, obj), Element::Keyword(obj) => compare_keyword(source, emacs, obj),
Element::BabelCall(obj) => compare_babel_call(source, emacs, obj),
Element::LatexEnvironment(obj) => compare_latex_environment(source, emacs, obj), Element::LatexEnvironment(obj) => compare_latex_environment(source, emacs, obj),
}; };
match compare_result { match compare_result {
@@ -487,7 +492,7 @@ fn compare_heading<'s>(
if rust.stars.to_string() != level { if rust.stars.to_string() != level {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
"Headline level do not much (emacs != rust): {} != {}", "Headline level do not match (emacs != rust): {} != {}",
level, rust.stars level, rust.stars
)) ))
} }
@@ -510,9 +515,9 @@ fn compare_heading<'s>(
.map(Token::as_atom) .map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))? .map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil"); .unwrap_or("nil");
match (todo_keyword, rust.todo_keyword, unquote(todo_keyword)) { match (todo_keyword, &rust.todo_keyword, unquote(todo_keyword)) {
("nil", None, _) => {} ("nil", None, _) => {}
(_, Some(rust_todo), Ok(emacs_todo)) if emacs_todo == rust_todo => {} (_, Some((_rust_todo_type, rust_todo)), Ok(emacs_todo)) if emacs_todo == *rust_todo => {}
(emacs_todo, rust_todo, _) => { (emacs_todo, rust_todo, _) => {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
@@ -521,6 +526,24 @@ fn compare_heading<'s>(
)); ));
} }
}; };
// Compare todo-type
let todo_type = get_property(emacs, ":todo-type")?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
// todo-type is an unquoted string either todo, done, or nil
match (todo_type, &rust.todo_keyword) {
("nil", None) => {}
("todo", Some((TodoKeywordType::Todo, _))) => {}
("done", Some((TodoKeywordType::Done, _))) => {}
(emacs_todo, rust_todo) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"(emacs != rust) {:?} != {:?}",
emacs_todo, rust_todo
));
}
};
// Compare title // Compare title
let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?; let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?;
@@ -532,7 +555,57 @@ fn compare_heading<'s>(
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?); child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
// TODO: Compare todo-type, priority, :footnote-section-p, :archivedp, :commentedp // Compare priority
let priority = get_property(emacs, ":priority")?;
match (priority, rust.priority_cookie) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
priority, rust.priority_cookie
));
}
(Some(emacs_priority_cookie), Some(rust_priority_cookie)) => {
let emacs_priority_cookie =
emacs_priority_cookie.as_atom()?.parse::<PriorityCookie>()?;
if emacs_priority_cookie != rust_priority_cookie {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
emacs_priority_cookie, rust_priority_cookie
));
}
}
}
// Compare archived
let archived = get_property(emacs, ":archivedp")?;
match (archived, rust.is_archived) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"archived mismatch (emacs != rust) {:?} != {:?}",
archived, rust.is_archived
));
}
(None, false) | (Some(_), true) => {}
}
// Compare commented
let commented = get_property(emacs, ":commentedp")?;
match (commented, rust.is_comment) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"commented mismatch (emacs != rust) {:?} != {:?}",
commented, rust.is_comment
));
}
(None, false) | (Some(_), true) => {}
}
// TODO: Compare :footnote-section-p
// Compare section // Compare section
let section_status = children let section_status = children
@@ -714,6 +787,8 @@ fn compare_plain_list_item<'s>(
contents_status, contents_status,
)?); )?);
// TODO: compare :bullet :checkbox :counter :pre-blank
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),
@@ -737,7 +812,7 @@ fn compare_greater_block<'s>(
let emacs_name = match rust.name.to_lowercase().as_str() { let emacs_name = match rust.name.to_lowercase().as_str() {
"center" => "center-block", "center" => "center-block",
"quote" => "quote-block", "quote" => "quote-block",
_ => todo!(), _ => "special-block",
}; };
if assert_name(emacs, emacs_name).is_err() { if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
@@ -914,7 +989,7 @@ fn compare_property_drawer<'s>(
rust: &'s PropertyDrawer<'s>, rust: &'s PropertyDrawer<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> { ) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?; let children = emacs.as_list()?;
let child_status = Vec::new(); let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut message = None; let mut message = None;
let emacs_name = "property-drawer"; let emacs_name = "property-drawer";
@@ -930,9 +1005,8 @@ fn compare_property_drawer<'s>(
Ok(_) => {} Ok(_) => {}
}; };
for (_emacs_child, _rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
// TODO: What are node properties and are they the only legal child of property drawers? child_status.push(compare_node_property(source, emacs_child, rust_child)?);
// child_status.push(compare_element(source, emacs_child, rust_child)?);
} }
Ok(DiffResult { Ok(DiffResult {
@@ -946,6 +1020,40 @@ fn compare_property_drawer<'s>(
.into()) .into())
} }
fn compare_node_property<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s NodeProperty<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "node-property";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
match assert_bounds(source, emacs, rust) {
Err(err) => {
this_status = DiffStatus::Bad;
message = Some(err.to_string())
}
Ok(_) => {}
};
// TODO: Compare :key :value
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message,
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into())
}
fn compare_table<'s>( fn compare_table<'s>(
source: &'s str, source: &'s str,
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
@@ -968,6 +1076,44 @@ fn compare_table<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// Compare formulas
//
// :tblfm is either nil or a list () filled with quoted strings containing the value for any tblfm keywords at the end of the table.
let emacs_formulas = get_property(emacs, ":tblfm")?;
if let Some(emacs_formulas) = emacs_formulas {
let emacs_formulas = emacs_formulas.as_list()?;
if emacs_formulas.len() != rust.formulas.len() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
} else {
let atoms = emacs_formulas
.into_iter()
.map(Token::as_atom)
.collect::<Result<Vec<_>, _>>()?;
let unquoted = atoms
.into_iter()
.map(unquote)
.collect::<Result<BTreeSet<_>, _>>()?;
for kw in &rust.formulas {
if !unquoted.contains(kw.value) {
this_status = DiffStatus::Bad;
message = Some(format!("Could not find formula in emacs: {}", kw.value))
}
}
}
} else {
if !rust.formulas.is_empty() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
}
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_row(source, emacs_child, rust_child)?); child_status.push(compare_table_row(source, emacs_child, rust_child)?);
} }
@@ -1005,6 +1151,10 @@ fn compare_table_row<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// TODO: Compare :type
//
// :type is an unquoted atom of either standard or rule
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_cell(source, emacs_child, rust_child)?); child_status.push(compare_table_cell(source, emacs_child, rust_child)?);
} }
@@ -1203,6 +1353,8 @@ fn compare_src_block<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// TODO: Compare :language :switches :parameters :number-lines :preserve-indent :retain-labels :use-labels :label-fmt :value
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),
@@ -1392,6 +1544,77 @@ fn compare_keyword<'s>(
Ok(_) => {} Ok(_) => {}
}; };
let key = unquote(
get_property(emacs, ":key")?
.ok_or("Emacs keywords should have a :key")?
.as_atom()?,
)?;
if key != rust.key.to_uppercase() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Mismatchs keyword keys (emacs != rust) {:?} != {:?}",
key, rust.key
))
}
let value = unquote(
get_property(emacs, ":value")?
.ok_or("Emacs keywords should have a :value")?
.as_atom()?,
)?;
if value != rust.value {
this_status = DiffStatus::Bad;
message = Some(format!(
"Mismatchs keyword values (emacs != rust) {:?} != {:?}",
value, rust.value
))
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message,
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into())
}
fn compare_babel_call<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Keyword<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "babel-call";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
match assert_bounds(source, emacs, rust) {
Err(err) => {
this_status = DiffStatus::Bad;
message = Some(err.to_string())
}
Ok(_) => {}
};
// TODO: compare :call :inside-header :arguments :end-header
let value = unquote(
get_property(emacs, ":value")?
.ok_or("Emacs keywords should have a :value")?
.as_atom()?,
)?;
if value != rust.value {
this_status = DiffStatus::Bad;
message = Some(format!(
"Mismatchs keyword values (emacs != rust) {:?} != {:?}",
value, rust.value
))
}
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),
@@ -1457,7 +1680,7 @@ fn compare_plain_text<'s>(
.as_atom()? .as_atom()?
.parse()?; .parse()?;
let emacs_text_length = end_ind - start_ind; let emacs_text_length = end_ind - start_ind;
if rust_source.len() != emacs_text_length { if rust_source.chars().count() != emacs_text_length {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
"(emacs len != rust len) {:?} != {:?}", "(emacs len != rust len) {:?} != {:?}",

View File

@@ -1,7 +1,7 @@
mod compare;
mod diff; mod diff;
mod parse; mod parse;
mod sexp;
mod util; mod util;
pub use diff::compare_document; pub use compare::run_anonymous_compare;
pub use parse::emacs_parse_org_document; pub use compare::run_compare_on_file;
pub use parse::get_emacs_version;
pub use parse::get_org_mode_version;

View File

@@ -1,6 +1,9 @@
use std::path::Path;
use std::process::Command; use std::process::Command;
pub fn emacs_parse_org_document<C>(file_contents: C) -> Result<String, Box<dyn std::error::Error>> pub fn emacs_parse_anonymous_org_document<C>(
file_contents: C,
) -> Result<String, Box<dyn std::error::Error>>
where where
C: AsRef<str>, C: AsRef<str>,
{ {
@@ -15,14 +18,46 @@ where
escaped_file_contents = escaped_file_contents escaped_file_contents = escaped_file_contents
); );
let mut cmd = Command::new("emacs"); let mut cmd = Command::new("emacs");
let proc = cmd let cmd = cmd
.arg("-q") .arg("-q")
.arg("--no-site-file") .arg("--no-site-file")
.arg("--no-splash") .arg("--no-splash")
.arg("--batch") .arg("--batch")
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = proc.output()?; let out = cmd.output()?;
out.status.exit_ok()?;
let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?)
}
pub fn emacs_parse_file_org_document<P>(file_path: P) -> Result<String, Box<dyn std::error::Error>>
where
P: AsRef<Path>,
{
let file_path = file_path.as_ref().canonicalize()?;
let containing_directory = file_path.parent().ok_or(format!(
"Failed to get containing directory for path {}",
file_path.display()
))?;
let elisp_script = format!(
r#"(progn
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)"#
);
let mut cmd = Command::new("emacs");
let cmd = cmd
.current_dir(containing_directory)
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--insert")
.arg(file_path.as_os_str())
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
out.status.exit_ok()?; out.status.exit_ok()?;
let org_sexp = out.stderr; let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?) Ok(String::from_utf8(org_sexp)?)
@@ -55,7 +90,7 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
(message "%s" (version)) (message "%s" (version))
)"#; )"#;
let mut cmd = Command::new("emacs"); let mut cmd = Command::new("emacs");
let proc = cmd let cmd = cmd
.arg("-q") .arg("-q")
.arg("--no-site-file") .arg("--no-site-file")
.arg("--no-splash") .arg("--no-splash")
@@ -63,7 +98,7 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = proc.output()?; let out = cmd.output()?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) Ok(String::from_utf8(out.stderr)?)
} }
@@ -74,7 +109,7 @@ pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
(message "%s" (org-version nil t nil)) (message "%s" (org-version nil t nil))
)"#; )"#;
let mut cmd = Command::new("emacs"); let mut cmd = Command::new("emacs");
let proc = cmd let cmd = cmd
.arg("-q") .arg("-q")
.arg("--no-site-file") .arg("--no-site-file")
.arg("--no-splash") .arg("--no-splash")
@@ -82,7 +117,7 @@ pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = proc.output()?; let out = cmd.output()?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) Ok(String::from_utf8(out.stderr)?)
} }

View File

@@ -16,9 +16,6 @@ use nom::sequence::delimited;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::convert_error;
use super::org_source::OrgSource;
use super::util::get_consumed;
use crate::error::Res; use crate::error::Res;
#[derive(Debug)] #[derive(Debug)]
@@ -31,8 +28,8 @@ pub enum Token<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct TextWithProperties<'s> { pub struct TextWithProperties<'s> {
pub text: &'s str, pub(crate) text: &'s str,
pub properties: Vec<Token<'s>>, pub(crate) properties: Vec<Token<'s>>,
} }
enum ParseState { enum ParseState {
@@ -41,35 +38,39 @@ enum ParseState {
} }
impl<'s> Token<'s> { impl<'s> Token<'s> {
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> { pub(crate) fn as_vector<'p>(
&'p self,
) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::Vector(children) => Ok(children), Token::Vector(children) => Ok(children),
_ => Err(format!("wrong token type, expected vector: {:?}", self)), _ => Err(format!("wrong token type, expected vector: {:?}", self)),
}?) }?)
} }
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> { pub(crate) fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::List(children) => Ok(children), Token::List(children) => Ok(children),
_ => Err(format!("wrong token type, expected list: {:?}", self)), _ => Err(format!("wrong token type, expected list: {:?}", self)),
}?) }?)
} }
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> { pub(crate) fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::Atom(body) => Ok(*body), Token::Atom(body) => Ok(*body),
_ => Err(format!("wrong token type, expected atom: {:?}", self)), _ => Err(format!("wrong token type, expected atom: {:?}", self)),
}?) }?)
} }
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> { pub(crate) fn as_text<'p>(
&'p self,
) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::TextWithProperties(body) => Ok(body), Token::TextWithProperties(body) => Ok(body),
_ => Err(format!("wrong token type, expected text: {:?}", self)), _ => Err(format!("wrong token type, expected text: {:?}", self)),
}?) }?)
} }
pub fn as_map<'p>( pub(crate) fn as_map<'p>(
&'p self, &'p self,
) -> Result<HashMap<&'s str, &'p Token<'s>>, Box<dyn std::error::Error>> { ) -> Result<HashMap<&'s str, &'p Token<'s>>, Box<dyn std::error::Error>> {
let mut hashmap = HashMap::new(); let mut hashmap = HashMap::new();
@@ -95,7 +96,26 @@ impl<'s> Token<'s> {
} }
} }
pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> { /// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize;
let parent_end = parent_start + parent.len();
let child_start = child.as_ptr() as usize;
let child_end = child_start + child.len();
child_start >= parent_start && child_end <= parent_end
}
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
assert!(is_slice_of(input, remaining));
let source = {
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
&input[..offset]
};
source.into()
}
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut out = String::with_capacity(text.len()); let mut out = String::with_capacity(text.len());
if !text.starts_with(r#"""#) { if !text.starts_with(r#"""#) {
return Err("Quoted text does not start with quote.".into()); return Err("Quoted text does not start with quote.".into());
@@ -132,29 +152,20 @@ pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { pub fn sexp<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = multispace0(input)?; let (remaining, _) = multispace0(input)?;
let remaining = OrgSource::new(remaining); let (remaining, tkn) = token(remaining).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
let (remaining, tkn) = token(remaining)
.map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
let (remaining, _) = multispace0(remaining)?; let (remaining, _) = multispace0(remaining)?;
Ok((remaining, tkn)) Ok((remaining, tkn))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn token<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, tkn) = token(input)?;
Ok((remaining, tkn))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn token<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
alt((list, vector, atom))(input) alt((list, vector, atom))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let (remaining, children) = delimited( let (remaining, children) = delimited(
multispace0, multispace0,
@@ -166,7 +177,7 @@ fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let (remaining, children) = delimited( let (remaining, children) = delimited(
multispace0, multispace0,
@@ -178,7 +189,7 @@ fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
not(peek(one_of(")]")))(input)?; not(peek(one_of(")]")))(input)?;
alt(( alt((
text_with_properties, text_with_properties,
@@ -189,7 +200,7 @@ fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, body) = take_till1(|c| match c { let (remaining, body) = take_till1(|c| match c {
' ' | '\t' | '\r' | '\n' | ')' | ']' => true, ' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
_ => false, _ => false,
@@ -198,7 +209,7 @@ fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag(r#"""#)(input)?; let (remaining, _) = tag(r#"""#)(input)?;
let (remaining, _) = escaped( let (remaining, _) = escaped(
take_till1(|c| match c { take_till1(|c| match c {
@@ -214,7 +225,7 @@ fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("#<")(input)?; let (remaining, _) = tag("#<")(input)?;
let (remaining, _body) = take_till1(|c| match c { let (remaining, _body) = take_till1(|c| match c {
'>' => true, '>' => true,
@@ -225,7 +236,7 @@ fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
Ok((remaining, Token::Atom(source.into()))) Ok((remaining, Token::Atom(source.into())))
} }
fn text_with_properties<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("#(")(input)?; let (remaining, _) = tag("#(")(input)?;
let (remaining, (text, props)) = delimited( let (remaining, (text, props)) = delimited(
multispace0, multispace0,
@@ -255,7 +266,7 @@ mod tests {
#[test] #[test]
fn simple() { fn simple() {
let input = " (foo bar baz ) "; let input = " (foo bar baz ) ";
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::Atom(_) => false, Token::Atom(_) => false,
@@ -268,7 +279,7 @@ mod tests {
#[test] #[test]
fn quoted() { fn quoted() {
let input = r#" ("foo" bar baz ) "#; let input = r#" ("foo" bar baz ) "#;
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::Atom(_) => false, Token::Atom(_) => false,
@@ -292,7 +303,7 @@ mod tests {
#[test] #[test]
fn quoted_containing_paren() { fn quoted_containing_paren() {
let input = r#" (foo "b(a)r" baz ) "#; let input = r#" (foo "b(a)r" baz ) "#;
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::List(_) => true, Token::List(_) => true,
@@ -328,7 +339,7 @@ mod tests {
#[test] #[test]
fn string_containing_escaped_characters() { fn string_containing_escaped_characters() {
let input = r#" (foo "\\( x=2 \\)" bar) "#; let input = r#" (foo "\\( x=2 \\)" bar) "#;
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::Atom(_) => false, Token::Atom(_) => false,

View File

@@ -1,5 +1,5 @@
use crate::parser::sexp::Token; use super::sexp::Token;
use crate::parser::Source; use crate::types::Source;
/// Check if the child string slice is a slice of the parent string slice. /// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool { fn is_slice_of(parent: &str, child: &str) -> bool {
@@ -13,7 +13,7 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get the offset into source that the rust object exists at. /// Get the offset into source that the rust object exists at.
/// ///
/// These offsets are zero-based unlike the elisp ones. /// These offsets are zero-based unlike the elisp ones.
pub fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) { fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) {
let rust_object_source = rust_object.get_source(); let rust_object_source = rust_object.get_source();
assert!(is_slice_of(source, rust_object_source)); assert!(is_slice_of(source, rust_object_source));
let offset = rust_object_source.as_ptr() as usize - source.as_ptr() as usize; let offset = rust_object_source.as_ptr() as usize - source.as_ptr() as usize;
@@ -21,7 +21,10 @@ pub fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (u
(offset, end) (offset, end)
} }
pub fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn assert_name<'s>(
emacs: &'s Token<'s>,
name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let children = emacs.as_list()?; let children = emacs.as_list()?;
let first_child = children let first_child = children
.first() .first()
@@ -37,7 +40,7 @@ pub fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn s
Ok(()) Ok(())
} }
pub fn assert_bounds<'s, S: Source<'s>>( pub(crate) fn assert_bounds<'s, S: Source<'s>>(
source: &'s str, source: &'s str,
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
rust: &'s S, rust: &'s S,
@@ -50,8 +53,11 @@ pub fn assert_bounds<'s, S: Source<'s>>(
standard_properties.end.ok_or("Token should have an end.")?, standard_properties.end.ok_or("Token should have an end.")?,
); );
let (rust_begin, rust_end) = get_offsets(source, rust); let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1) != begin || (rust_end + 1) != end { let rust_begin_char_offset = (&source[..rust_begin]).chars().count();
Err(format!("Rust bounds (in bytes) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?; let rust_end_char_offset =
rust_begin_char_offset + (&source[rust_begin..rust_end]).chars().count();
if (rust_begin_char_offset + 1) != begin || (rust_end_char_offset + 1) != end {
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset + 1, rust_end = rust_end_char_offset + 1, emacs_begin=begin, emacs_end=end))?;
} }
Ok(()) Ok(())
@@ -138,7 +144,12 @@ fn maybe_token_to_usize(
.map_or(Ok(None), |r| r.map(Some))?) .map_or(Ok(None), |r| r.map(Some))?)
} }
pub fn get_property<'s, 'x>( /// Get a named property from the emacs token.
///
/// Returns Ok(None) if value is nil.
///
/// Returns error if the attribute is not specified on the token at all.
pub(crate) fn get_property<'s, 'x>(
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
key: &'x str, key: &'x str,
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> { ) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {

185
src/context/context.rs Normal file
View File

@@ -0,0 +1,185 @@
use std::marker::PhantomData;
use nom::combinator::eof;
use nom::IResult;
use super::exiting::ExitClass;
use super::global_settings::GlobalSettings;
use super::list::List;
use super::DynContextMatcher;
use super::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::OrgSource;
#[derive(Debug)]
pub(crate) enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>),
/// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str),
/// Stores the name of the current object to prevent directly nesting elements of the same type.
ContextObject(&'r str),
/// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool),
/// 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>),
}
pub(crate) struct ExitMatcherNode<'r> {
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
pub(crate) exit_matcher: &'r DynContextMatcher<'r>,
pub(crate) class: ExitClass,
}
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ExitMatcherNode");
formatter.field("class", &self.class.to_string());
formatter.finish()
}
}
#[derive(Debug)]
pub(crate) struct Context<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>,
}
impl<'g, 'r, 's> Context<'g, 'r, 's> {
pub(crate) fn new(
global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>,
) -> Self {
Self {
global_settings,
tree,
}
}
pub(crate) fn with_additional_node(&'r self, new_element: &'r ContextElement<'r, 's>) -> Self {
let new_tree = self.tree.push(new_element);
Self::new(self.global_settings, new_tree)
}
pub(crate) fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> {
self.tree.iter()
}
fn iter_context(&'r self) -> Iter<'g, 'r, 's> {
Iter {
next: self.tree.iter_list(),
global_settings: self.global_settings,
}
}
pub(crate) fn get_parent(&'r self) -> Option<Self> {
self.tree.get_parent().map(|parent_tree| Self {
global_settings: self.global_settings,
tree: parent_tree.clone(),
})
}
fn get_data(&self) -> &ContextElement<'r, 's> {
self.tree.get_data()
}
pub(crate) fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> {
self.global_settings
}
pub(crate) fn with_global_settings<'gg>(
&self,
new_settings: &'gg GlobalSettings<'gg, 's>,
) -> Context<'gg, 'r, 's> {
Context {
global_settings: new_settings,
tree: self.tree.clone(),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn check_exit_matcher(
&'r self,
i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
let mut current_class_filter = ExitClass::Gamma;
for current_node in self.iter_context() {
let context_element = current_node.get_data();
match context_element {
ContextElement::ExitMatcherNode(exit_matcher) => {
if exit_matcher.class as u32 <= current_class_filter as u32 {
current_class_filter = exit_matcher.class;
let local_result = (exit_matcher.exit_matcher)(&current_node, i);
if local_result.is_ok() {
return local_result;
}
}
}
_ => {}
};
}
// TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoExit".into(),
))));
}
/// Indicates if elements should consume the whitespace after them.
///
/// Defaults to true.
pub(crate) fn should_consume_trailing_whitespace(&self) -> bool {
self._should_consume_trailing_whitespace().unwrap_or(true)
}
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
for current_node in self.iter() {
match current_node {
ContextElement::ConsumeTrailingWhitespace(should) => {
return Some(*should);
}
_ => {}
}
}
None
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn document_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
eof(input)
}
struct Iter<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>,
next: super::list::IterList<'r, &'r ContextElement<'r, 's>>,
}
impl<'g, 'r, 's> Iterator for Iter<'g, 'r, 's> {
type Item = Context<'g, 'r, 's>;
fn next(&mut self) -> Option<Self::Item> {
let next_tree = self.next.next();
let ret =
next_tree.map(|parent_tree| Context::new(self.global_settings, parent_tree.clone()));
ret
}
}
impl<'r, 's> ContextElement<'r, 's> {
pub(crate) fn document_context() -> Self {
Self::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &document_end,
class: ExitClass::Document,
})
}
}

13
src/context/exiting.rs Normal file
View File

@@ -0,0 +1,13 @@
#[derive(Debug, Copy, Clone)]
pub(crate) enum ExitClass {
Document = 1,
Alpha = 2,
Beta = 3,
Gamma = 4,
}
impl std::fmt::Display for ExitClass {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

View File

@@ -0,0 +1,23 @@
use std::fmt::Debug;
use std::path::PathBuf;
pub trait FileAccessInterface: Debug {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
}
#[derive(Debug, Clone)]
pub struct LocalFileAccessInterface {
pub working_directory: Option<PathBuf>,
}
impl FileAccessInterface for LocalFileAccessInterface {
fn read_file(&self, path: &str) -> Result<String, std::io::Error> {
let final_path = self
.working_directory
.as_ref()
.map(PathBuf::as_path)
.map(|pb| pb.join(path))
.unwrap_or_else(|| PathBuf::from(path));
Ok(std::fs::read_to_string(final_path)?)
}
}

View File

@@ -0,0 +1,34 @@
use std::collections::BTreeSet;
use super::FileAccessInterface;
use super::LocalFileAccessInterface;
use crate::types::Object;
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
#[derive(Debug, Clone)]
pub struct GlobalSettings<'g, 's> {
pub radio_targets: Vec<&'g Vec<Object<'s>>>,
pub file_access: &'g dyn FileAccessInterface,
pub in_progress_todo_keywords: BTreeSet<String>,
pub complete_todo_keywords: BTreeSet<String>,
}
impl<'g, 's> GlobalSettings<'g, 's> {
fn new() -> GlobalSettings<'g, 's> {
GlobalSettings {
radio_targets: Vec::new(),
file_access: &LocalFileAccessInterface {
working_directory: None,
},
in_progress_todo_keywords: BTreeSet::new(),
complete_todo_keywords: BTreeSet::new(),
}
}
}
impl<'g, 's> Default for GlobalSettings<'g, 's> {
fn default() -> GlobalSettings<'g, 's> {
GlobalSettings::new()
}
}

69
src/context/list.rs Normal file
View File

@@ -0,0 +1,69 @@
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub(crate) struct List<'parent, T> {
data: T,
parent: Link<'parent, T>,
}
type Link<'parent, T> = Option<&'parent List<'parent, T>>;
impl<'parent, T> List<'parent, T> {
pub(crate) fn new(first_item: T) -> Self {
Self {
data: first_item,
parent: None,
}
}
pub(crate) fn get_data(&self) -> &T {
&self.data
}
pub(crate) fn get_parent(&'parent self) -> Link<'parent, T> {
self.parent
}
pub(crate) fn iter(&self) -> Iter<'_, T> {
Iter { next: Some(self) }
}
pub(crate) fn iter_list(&self) -> IterList<'_, T> {
IterList { next: Some(self) }
}
pub(crate) fn push(&'parent self, item: T) -> Self {
Self {
data: item,
parent: Some(self),
}
}
}
pub(crate) struct Iter<'a, T> {
next: Link<'a, T>,
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next.map(|link| link.get_data());
self.next = self.next.map(|link| link.get_parent()).flatten();
ret
}
}
pub(crate) struct IterList<'a, T> {
next: Link<'a, T>,
}
impl<'a, T> Iterator for IterList<'a, T> {
type Item = &'a List<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next;
self.next = self.next.map(|this| this.get_parent()).flatten();
ret
}
}

29
src/context/mod.rs Normal file
View File

@@ -0,0 +1,29 @@
use crate::error::Res;
use crate::parser::OrgSource;
mod context;
mod exiting;
mod file_access_interface;
mod global_settings;
mod list;
mod parser_with_context;
pub(crate) type RefContext<'b, 'g, 'r, 's> = &'b Context<'g, 'r, 's>;
pub(crate) trait ContextMatcher = for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>>;
type DynContextMatcher<'c> = dyn ContextMatcher + 'c;
pub(crate) trait Matcher = for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
#[allow(dead_code)]
type DynMatcher<'c> = dyn Matcher + 'c;
pub(crate) use context::Context;
pub(crate) use context::ContextElement;
pub(crate) use context::ExitMatcherNode;
pub(crate) use exiting::ExitClass;
pub use file_access_interface::FileAccessInterface;
pub use file_access_interface::LocalFileAccessInterface;
pub use global_settings::GlobalSettings;
pub(crate) use list::List;
pub(crate) use parser_with_context::parser_with_context;

View File

@@ -2,17 +2,18 @@ use nom::error::ErrorKind;
use nom::error::ParseError; use nom::error::ParseError;
use nom::IResult; use nom::IResult;
pub type Res<T, U> = IResult<T, U, CustomError<T>>; pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>;
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now. // TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum CustomError<I> { pub enum CustomError<I> {
MyError(MyError<I>), MyError(MyError<I>),
Nom(I, ErrorKind), Nom(I, ErrorKind),
IO(std::io::Error),
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct MyError<I>(pub I); pub struct MyError<I>(pub(crate) I);
impl<I> ParseError<I> for CustomError<I> { impl<I> ParseError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self { fn from_error_kind(input: I, kind: ErrorKind) -> Self {
@@ -24,3 +25,9 @@ impl<I> ParseError<I> for CustomError<I> {
other other
} }
} }
impl<I> From<std::io::Error> for CustomError<I> {
fn from(value: std::io::Error) -> Self {
CustomError::IO(value)
}
}

View File

@@ -1,4 +1,4 @@
mod error; mod error;
pub use error::CustomError; pub(crate) use error::CustomError;
pub use error::MyError; pub(crate) use error::MyError;
pub use error::Res; pub(crate) use error::Res;

View File

@@ -10,7 +10,7 @@ const SERVICE_NAME: &'static str = "organic";
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits. // Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
// by default it will hit http://localhost:4317 with a gRPC payload // by default it will hit http://localhost:4317 with a gRPC payload
// TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned. // TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned.
let exporter = opentelemetry_otlp::new_exporter() let exporter = opentelemetry_otlp::new_exporter()
@@ -55,17 +55,17 @@ pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
} }
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
opentelemetry::global::shutdown_tracer_provider(); opentelemetry::global::shutdown_tracer_provider();
Ok(()) Ok(())
} }
#[cfg(not(feature = "tracing"))] #[cfg(not(feature = "tracing"))]
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "tracing"))] #[cfg(not(feature = "tracing"))]
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }

View File

@@ -1,16 +1,16 @@
#![feature(round_char_boundary)] #![feature(round_char_boundary)]
#![feature(exit_status_error)] #![feature(exit_status_error)]
#![feature(trait_alias)]
// TODO: #![warn(missing_docs)]
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
mod compare; pub mod compare;
#[cfg(feature = "compare")]
pub use compare::compare_document;
#[cfg(feature = "compare")]
pub use compare::emacs_parse_org_document;
#[cfg(feature = "compare")]
pub use compare::get_emacs_version;
#[cfg(feature = "compare")]
pub use compare::get_org_mode_version;
mod context;
mod error; mod error;
pub mod parser; pub mod parser;
pub mod types;
pub use context::FileAccessInterface;
pub use context::GlobalSettings;
pub use context::LocalFileAccessInterface;

View File

@@ -1,17 +1,12 @@
#![feature(round_char_boundary)] #![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
use std::io::Read; use std::io::Read;
use std::path::Path;
use ::organic::parser::document; use ::organic::parser::parse;
#[cfg(feature = "compare")] use organic::parser::parse_with_settings;
use organic::compare_document; use organic::GlobalSettings;
#[cfg(feature = "compare")] use organic::LocalFileAccessInterface;
use organic::emacs_parse_org_document;
#[cfg(feature = "compare")]
use organic::get_emacs_version;
#[cfg(feature = "compare")]
use organic::get_org_mode_version;
#[cfg(feature = "compare")]
use organic::parser::sexp::sexp_with_padding;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry; use crate::init_tracing::init_telemetry;
@@ -39,8 +34,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> { fn main_body() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args().skip(1);
if args.is_empty() {
let org_contents = read_stdin_to_string()?; let org_contents = read_stdin_to_string()?;
run_compare(org_contents) run_anonymous_parse(org_contents)
} else {
for arg in args {
run_parse_on_file(arg)?
}
Ok(())
}
} }
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> { fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
@@ -51,42 +54,28 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
Ok(stdin_contents) Ok(stdin_contents)
} }
#[cfg(feature = "compare")] fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> { let rust_parsed = parse(org_contents.as_ref())?;
let emacs_version = get_emacs_version()?; println!("{:#?}", rust_parsed);
let org_mode_version = get_org_mode_version()?; Ok(())
let org_contents = org_contents.as_ref(); }
eprintln!("Using emacs version: {}", emacs_version.trim());
eprintln!("Using org-mode version: {}", org_mode_version.trim()); fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let (remaining, rust_parsed) = document(org_contents).map_err(|e| e.to_string())?; let org_path = org_path.as_ref();
let org_sexp = emacs_parse_org_document(org_contents)?; let parent_directory = org_path
let (_remaining, parsed_sexp) = .parent()
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?; .ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
println!("{}\n\n\n", org_contents); let org_contents = org_contents.as_str();
println!("{}", org_sexp); let file_access_interface = LocalFileAccessInterface {
println!("{:#?}", rust_parsed); working_directory: Some(parent_directory.to_path_buf()),
};
// We do the diffing after printing out both parsed forms in case the diffing panics let global_settings = {
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let mut global_settings = GlobalSettings::default();
diff_result.print(org_contents)?; global_settings.file_access = &file_access_interface;
global_settings
if diff_result.is_bad() { };
Err("Diff results do not match.")?; let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
}
if remaining != "" {
Err(format!("There was unparsed text remaining: {}", remaining))?;
}
Ok(())
}
#[cfg(not(feature = "compare"))]
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let (_remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
Ok(()) Ok(())
} }

View File

@@ -5,20 +5,21 @@ use nom::combinator::recognize;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_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::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::plain_link::protocol; use crate::parser::plain_link::protocol;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::AngleLink; use crate::types::AngleLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn angle_link<'r, 's>( pub(crate) fn angle_link<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, AngleLink<'s>> { ) -> Res<OrgSource<'s>, AngleLink<'s>> {
let (remaining, _) = tag("<")(input)?; let (remaining, _) = tag("<")(input)?;
@@ -26,6 +27,8 @@ pub fn angle_link<'r, 's>(
let (remaining, _separator) = tag(":")(remaining)?; let (remaining, _separator) = tag(":")(remaining)?;
let (remaining, path) = path_angle(context, remaining)?; let (remaining, path) = path_angle(context, remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -38,15 +41,15 @@ pub fn angle_link<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle<'r, 's>( fn path_angle<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &path_angle_end, exit_matcher: &path_angle_end,
})); });
let parser_context = context.with_additional_node(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -55,8 +58,8 @@ fn path_angle<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle_end<'r, 's>( fn path_angle_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag(">")(input) tag(">")(input)

View File

@@ -2,7 +2,6 @@ use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -14,24 +13,26 @@ use nom::sequence::tuple;
use super::citation_reference::must_balance_bracket; use super::citation_reference::must_balance_bracket;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_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::CustomError;
use crate::error::Res; use crate::error::Res;
use crate::parser::citation_reference::citation_reference; use crate::parser::citation_reference::citation_reference;
use crate::parser::citation_reference::citation_reference_key; use crate::parser::citation_reference::citation_reference_key;
use crate::parser::exiting::ExitClass;
use crate::parser::object::Citation;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::Object; use crate::types::Citation;
use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation<'r, 's>( pub(crate) fn citation<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Citation<'s>> { ) -> Res<OrgSource<'s>, Citation<'s>> {
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix. // TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
@@ -48,7 +49,8 @@ pub fn citation<'r, 's>(
parser_with_context!(global_suffix)(context), parser_with_context!(global_suffix)(context),
))))(remaining)?; ))))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -59,7 +61,7 @@ pub fn citation<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn citestyle<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tuple((tag("/"), style))(input)?; let (remaining, _) = tuple((tag("/"), style))(input)?;
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?; let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -67,30 +69,30 @@ fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn style<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn style<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| { recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-".contains(*c) c.is_alphanumeric() || "_-".contains(*c)
})))(input) })))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn variant<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn variant<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| { recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-/".contains(*c) c.is_alphanumeric() || "_-/".contains(*c)
})))(input) })))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix<'r, 's>( fn global_prefix<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_prefix_end(input.get_bracket_depth()); let exit_with_depth = global_prefix_end(input.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
@@ -102,17 +104,13 @@ fn global_prefix<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn global_prefix_end( fn global_prefix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
starting_bracket_depth: BracketDepth, move |context, input: OrgSource<'_>| _global_prefix_end(context, input, starting_bracket_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_prefix_end(context, input, starting_bracket_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _global_prefix_end<'r, 's>( fn _global_prefix_end<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -134,16 +132,16 @@ fn _global_prefix_end<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix<'r, 's>( fn global_suffix<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_suffix_end(input.get_bracket_depth()); let exit_with_depth = global_suffix_end(input.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
@@ -154,17 +152,13 @@ fn global_suffix<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn global_suffix_end( fn global_suffix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
starting_bracket_depth: BracketDepth, move |context, input: OrgSource<'_>| _global_suffix_end(context, input, starting_bracket_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_suffix_end(context, input, starting_bracket_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _global_suffix_end<'r, 's>( fn _global_suffix_end<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -188,19 +182,23 @@ fn _global_suffix_end<'r, 's>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::parser_context::ContextTree; use crate::types::Element;
use crate::parser::parser_with_context::parser_with_context; use crate::types::Source;
use crate::parser::source::Source;
#[test] #[test]
fn citation_simple() { fn citation_simple() {
let input = OrgSource::new("[cite:@foo]"); let input = OrgSource::new("[cite:@foo]");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = parser_with_context!(element(true))(&initial_context); let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph"); let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let first_paragraph = match first_paragraph { let first_paragraph = match first_paragraph {
crate::parser::Element::Paragraph(paragraph) => paragraph, Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");

View File

@@ -12,24 +12,25 @@ use nom::sequence::tuple;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; 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::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::object::CitationReference;
use crate::parser::object_parser::minimal_set_object; use crate::parser::object_parser::minimal_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::parser::Object; use crate::types::CitationReference;
use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference<'r, 's>( pub(crate) fn citation_reference<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CitationReference<'s>> { ) -> Res<OrgSource<'s>, CitationReference<'s>> {
let (remaining, _prefix) = let (remaining, _prefix) =
@@ -48,8 +49,8 @@ pub fn citation_reference<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference_key<'r, 's>( pub(crate) fn citation_reference_key<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((
@@ -68,16 +69,16 @@ pub fn citation_reference_key<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_prefix<'r, 's>( fn key_prefix<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = key_prefix_end(input.get_bracket_depth()); let exit_with_depth = key_prefix_end(input.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(minimal_set_object)(&parser_context), parser_with_context!(minimal_set_object)(&parser_context),
@@ -89,16 +90,16 @@ fn key_prefix<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_suffix<'r, 's>( fn key_suffix<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = key_suffix_end(input.get_bracket_depth()); let exit_with_depth = key_suffix_end(input.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(minimal_set_object)(&parser_context), parser_with_context!(minimal_set_object)(&parser_context),
@@ -109,17 +110,13 @@ fn key_suffix<'r, 's>(
Ok((remaining, children)) Ok((remaining, children))
} }
fn key_prefix_end( fn key_prefix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
starting_bracket_depth: BracketDepth, move |context, input: OrgSource<'_>| _key_prefix_end(context, input, starting_bracket_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_key_prefix_end(context, input, starting_bracket_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _key_prefix_end<'r, 's>( fn _key_prefix_end<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -140,17 +137,13 @@ fn _key_prefix_end<'r, 's>(
))(input) ))(input)
} }
fn key_suffix_end( fn key_suffix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
starting_bracket_depth: BracketDepth, move |context, input: OrgSource<'_>| _key_suffix_end(context, input, starting_bracket_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_key_suffix_end(context, input, starting_bracket_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _key_suffix_end<'r, 's>( fn _key_suffix_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -168,7 +161,7 @@ fn _key_suffix_end<'r, 's>(
tag(";")(input) tag(";")(input)
} }
pub fn must_balance_bracket<'s, F, O>( pub(crate) fn must_balance_bracket<'s, F, O>(
mut inner: F, mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O> ) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>
where where

View File

@@ -12,16 +12,16 @@ use nom::combinator::verify;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Clock; use crate::types::Clock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn clock<'r, 's>( pub(crate) fn clock<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Clock<'s>> { ) -> Res<OrgSource<'s>, Clock<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -44,8 +44,8 @@ pub fn clock<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp_range_duration<'r, 's>( fn inactive_timestamp_range_duration<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((
@@ -66,8 +66,8 @@ fn inactive_timestamp_range_duration<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'r, 's>( fn inactive_timestamp<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((

View File

@@ -13,20 +13,20 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::Context; use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Comment; use crate::types::Comment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment<'r, 's>( pub(crate) fn comment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> { ) -> Res<OrgSource<'s>, Comment<'s>> {
if immediate_in_section(context, "comment") { if immediate_in_section(context, "comment") {
@@ -34,7 +34,8 @@ pub fn comment<'r, 's>(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
} }
let parser_context = context.with_additional_node(ContextElement::Context("comment")); let parser_context = ContextElement::Context("comment");
let parser_context = context.with_additional_node(&parser_context);
let comment_line_matcher = parser_with_context!(comment_line)(&parser_context); let comment_line_matcher = parser_with_context!(comment_line)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, _first_line) = comment_line_matcher(input)?; let (remaining, _first_line) = comment_line_matcher(input)?;
@@ -51,8 +52,8 @@ pub fn comment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn comment_line<'r, 's>( fn comment_line<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -66,11 +67,24 @@ fn comment_line<'r, 's>(
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_comment<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((
start_of_line,
space0,
tag("#"),
alt((tag(" "), line_ending, eof)),
))(input)?;
Ok((input, ()))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::parser::parser_context::ContextTree; use crate::context::Context;
use crate::parser::parser_with_context::parser_with_context; use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
#[test] #[test]
fn require_space_after_hash() { fn require_space_after_hash() {
@@ -79,7 +93,9 @@ mod tests {
#not a comment #not a comment
# Comment again", # Comment again",
); );
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let comment_matcher = parser_with_context!(comment)(&initial_context); let comment_matcher = parser_with_context!(comment)(&initial_context);
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment"); let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
assert_eq!( assert_eq!(

View File

@@ -1,31 +1,26 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::recognize;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::sexp::sexp; use crate::context::RefContext;
use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::DiarySexp; use crate::types::DiarySexp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn diary_sexp<'r, 's>( pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> { ) -> Res<OrgSource<'s>, DiarySexp<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _clock) = tag("%%(")(input)?;
let (remaining, _clock) = tag("%%")(remaining)?; let (remaining, _contents) = is_not("\r\n")(remaining)?;
let (remaining, _gap_whitespace) = space0(remaining)?; let (remaining, _eol) = alt((line_ending, eof))(remaining)?;
let (remaining, _sexp) = recognize(sexp)(remaining)?;
let (remaining, _trailing_whitespace) =
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -35,3 +30,9 @@ pub fn diary_sexp<'r, 's>(
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag("%%(")))(input)?;
Ok((input, ()))
}

View File

@@ -1,112 +1,114 @@
use nom::branch::alt; use nom::combinator::all_consuming;
use nom::bytes::complete::tag;
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::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many1_count;
use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::element::Element; use super::headline::heading;
use super::object::Object; use super::in_buffer_settings::apply_in_buffer_settings;
use super::org_source::convert_error; use super::in_buffer_settings::scan_for_in_buffer_settings;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::section::zeroth_section;
use super::source::Source;
use super::token::AllTokensIterator; use super::token::AllTokensIterator;
use super::token::Token; use super::token::Token;
use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::start_of_line; use crate::context::parser_with_context;
use super::Context; use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::comment::comment; use crate::parser::org_source::convert_error;
use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::planning::planning;
use crate::parser::property_drawer::property_drawer;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; use crate::types::Document;
use crate::types::Object;
#[derive(Debug)] /// Parse a full org-mode document.
pub struct Document<'s> { ///
pub source: &'s str, /// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document.
pub zeroth_section: Option<Section<'s>>, #[allow(dead_code)]
pub children: Vec<Heading<'s>>, pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
parse_with_settings(input, &GlobalSettings::default())
} }
#[derive(Debug)] /// Parse a full org-mode document with starting settings.
pub struct Heading<'s> { ///
pub source: &'s str, /// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied.
pub stars: usize, ///
pub todo_keyword: Option<&'s str>, /// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
// TODO: add todo-type enum #[allow(dead_code)]
pub title: Vec<Object<'s>>, pub fn parse_with_settings<'g, 's>(
pub tags: Vec<&'s str>, input: &'s str,
pub children: Vec<DocumentElement<'s>>, global_settings: &'g GlobalSettings<'g, 's>,
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
let wrapped_input = OrgSource::new(input);
let ret =
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
.map_err(|err| err.to_string())
.map(|(_remaining, parsed_document)| parsed_document);
Ok(ret?)
} }
#[derive(Debug)] /// Parse a full org-mode document.
pub struct Section<'s> { ///
pub source: &'s str, /// Use this entry point when you want to have direct control over the starting context or if you want to use this integrated with other nom parsers. For general-purpose usage, the `parse` and `parse_with_settings` functions are a lot simpler.
pub children: Vec<Element<'s>>, ///
} /// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)]
#[derive(Debug)] fn document<'b, 'g, 'r, 's>(
pub enum DocumentElement<'s> { context: RefContext<'b, 'g, 'r, 's>,
Heading(Heading<'s>), input: &'s str,
Section(Section<'s>), ) -> Res<&'s str, Document<'s>> {
} let (remaining, doc) = document_org_source(context, input.into()).map_err(convert_error)?;
Ok((Into::<&str>::into(remaining), doc))
impl<'s> Source<'s> for Document<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for DocumentElement<'s> {
fn get_source(&'s self) -> &'s str {
match self {
DocumentElement::Heading(obj) => obj.source,
DocumentElement::Section(obj) => obj.source,
}
}
}
impl<'s> Source<'s> for Section<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Heading<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
#[allow(dead_code)] #[allow(dead_code)]
pub fn document(input: &str) -> Res<&str, Document> { fn document_org_source<'b, 'g, 'r, 's>(
let initial_context: ContextTree<'_, '_> = ContextTree::new(); context: RefContext<'b, 'g, 'r, 's>,
let wrapped_input = OrgSource::new(input); input: OrgSource<'s>,
let (remaining, document) = _document(&initial_context, wrapped_input) ) -> Res<OrgSource<'s>, Document<'s>> {
.map(|(rem, out)| (Into::<&str>::into(rem), out)) let mut final_settings = Vec::new();
.map_err(convert_error)?; let (_, document_settings) = scan_for_in_buffer_settings(input)?;
let setup_files: Vec<String> = document_settings
.iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("setupfile"))
.map(|kw| kw.value)
.map(|setup_file| {
context
.get_global_settings()
.file_access
.read_file(setup_file)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))
})
.collect::<Result<Vec<_>, _>>()?;
for setup_file in setup_files.iter().map(String::as_str) {
let (_, setup_file_settings) =
scan_for_in_buffer_settings(setup_file.into()).map_err(|_err| {
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
final_settings.extend(setup_file_settings);
}
final_settings.extend(document_settings);
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
.map_err(|_err| {
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
let new_context = context.with_global_settings(&new_settings);
let context = &new_context;
let (remaining, document) =
_document(context, input).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
{ {
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets. // If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
let all_radio_targets: Vec<&Vec<Object<'_>>> = document let all_radio_targets: Vec<&Vec<Object<'_>>> = document
@@ -122,11 +124,11 @@ pub fn document(input: &str) -> Res<&str, Document> {
.map(|rt| &rt.children) .map(|rt| &rt.children)
.collect(); .collect();
if !all_radio_targets.is_empty() { if !all_radio_targets.is_empty() {
let initial_context = initial_context let mut new_global_settings = context.get_global_settings().clone();
.with_additional_node(ContextElement::RadioTarget(all_radio_targets)); new_global_settings.radio_targets = all_radio_targets;
let (remaining, document) = _document(&initial_context, wrapped_input) let parser_context = context.with_global_settings(&new_global_settings);
.map(|(rem, out)| (Into::<&str>::into(rem), out)) let (remaining, document) = _document(&parser_context, input)
.map_err(convert_error)?; .map(|(rem, out)| (Into::<&str>::into(rem), out))?;
return Ok((remaining.into(), document)); return Ok((remaining.into(), document));
} }
} }
@@ -134,8 +136,8 @@ pub fn document(input: &str) -> Res<&str, Document> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _document<'r, 's>( fn _document<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> { ) -> Res<OrgSource<'s>, Document<'s>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context); let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
@@ -154,259 +156,8 @@ fn _document<'r, 's>(
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn zeroth_section<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("section"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}));
let without_consuming_whitespace_context =
parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false));
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, comment_and_property_drawer_element) = opt(tuple((
opt(parser_with_context!(comment)(
&without_consuming_whitespace_context,
)),
parser_with_context!(property_drawer)(context),
many0(blank_line),
)))(input)?;
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some()
},
)(remaining)?;
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
children.insert(0, Element::PropertyDrawer(property_drawer));
comment
.map(Element::Comment)
.map(|ele| children.insert(0, ele));
});
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section<'r, 's>(
context: Context<'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("section"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}));
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (planning_element, property_drawer_element)) = tuple((
opt(parser_with_context!(planning)(&parser_context)),
opt(parser_with_context!(property_drawer)(&parser_context)),
))(input)?;
if planning_element.is_none() && property_drawer_element.is_none() {
let (remain, _ws) = many0(blank_line)(remaining)?;
remaining = remain;
input = remain;
}
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
},
)(remaining)?;
property_drawer_element
.map(Element::PropertyDrawer)
.map(|ele| children.insert(0, ele));
planning_element
.map(Element::Planning)
.map(|ele| children.insert(0, ele));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input)
}
const fn heading(
parent_stars: usize,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Heading<'s>> {
move |context: Context, input: OrgSource<'_>| _heading(context, input, parent_stars)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
title,
tags: heading_tags,
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
OrgSource<'s>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
}));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let (
remaining,
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((heading_keyword, space1))),
many1(standard_set_object_matcher),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
ws,
maybe_todo_keyword,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
alt((tag("TODO"), tag("DONE")))(input)
}
impl<'s> Document<'s> { impl<'s> Document<'s> {
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> { fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
AllTokensIterator::new(Token::Document(self)) AllTokensIterator::new(Token::Document(self))
} }
} }

View File

@@ -11,29 +11,29 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::parser::Drawer; use crate::types::Drawer;
use crate::parser::Element; use crate::types::Element;
use crate::parser::Paragraph; use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn drawer<'r, 's>( pub(crate) fn drawer<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Drawer<'s>> { ) -> Res<OrgSource<'s>, Drawer<'s>> {
if immediate_in_section(context, "drawer") { if immediate_in_section(context, "drawer") {
@@ -50,13 +50,17 @@ pub fn drawer<'r, 's>(
recognize(tuple((space0, line_ending))), recognize(tuple((space0, line_ending))),
))(remaining)?; ))(remaining)?;
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context("drawer")) ContextElement::Context("drawer"),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &drawer_end, exit_matcher: &drawer_end,
})); }),
];
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 element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -98,8 +102,8 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn drawer_end<'r, 's>( fn drawer_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;

View File

@@ -12,28 +12,28 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::DynamicBlock;
use crate::parser::lesser_element::Paragraph;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Element; use crate::types::DynamicBlock;
use crate::types::Element;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn dynamic_block<'r, 's>( pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DynamicBlock<'s>> { ) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
// TODO: Do I need to differentiate between different dynamic block types. // TODO: Do I need to differentiate between different dynamic block types.
@@ -50,13 +50,17 @@ pub fn dynamic_block<'r, 's>(
opt(tuple((space1, parameters))), opt(tuple((space1, parameters))),
line_ending, line_ending,
))(remaining)?; ))(remaining)?;
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context("dynamic block")) ContextElement::Context("dynamic block"),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &dynamic_block_end, exit_matcher: &dynamic_block_end,
})); }),
];
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 { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -106,8 +110,8 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dynamic_block_end<'r, 's>( fn dynamic_block_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;

View File

@@ -4,15 +4,19 @@ use nom::multi::many0;
use super::clock::clock; use super::clock::clock;
use super::comment::comment; use super::comment::comment;
use super::comment::detect_comment;
use super::diary_sexp::detect_diary_sexp;
use super::diary_sexp::diary_sexp; use super::diary_sexp::diary_sexp;
use super::drawer::drawer; use super::drawer::drawer;
use super::dynamic_block::dynamic_block; use super::dynamic_block::dynamic_block;
use super::element::Element; use super::fixed_width_area::detect_fixed_width_area;
use super::fixed_width_area::fixed_width_area; use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::detect_footnote_definition;
use super::footnote_definition::footnote_definition; use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block; use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule; use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
use super::keyword::babel_call_keyword;
use super::keyword::keyword; use super::keyword::keyword;
use super::latex_environment::latex_environment; use super::latex_environment::latex_environment;
use super::lesser_block::comment_block; use super::lesser_block::comment_block;
@@ -24,25 +28,30 @@ use super::org_source::OrgSource;
use super::paragraph::paragraph; use super::paragraph::paragraph;
use super::plain_list::detect_plain_list; use super::plain_list::detect_plain_list;
use super::plain_list::plain_list; use super::plain_list::plain_list;
use super::source::SetSource; use super::table::detect_table;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::Context; use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::table::org_mode_table; use crate::parser::table::org_mode_table;
use crate::types::Element;
use crate::types::SetSource;
pub const fn element( pub(crate) const fn element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Element<'s>> { ) -> impl for<'b, 'g, 'r, 's> Fn(
move |context: Context, input: OrgSource<'_>| _element(context, input, can_be_paragraph) RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
move |context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _element<'r, 's>( fn _element<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>> {
@@ -64,6 +73,7 @@ fn _element<'r, 's>(
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context); let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context); let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context); let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context); let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context); let latex_environment_matcher = parser_with_context!(latex_environment)(context);
@@ -87,6 +97,7 @@ fn _element<'r, 's>(
map(fixed_width_area_matcher, Element::FixedWidthArea), map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule), map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment), map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword), map(keyword_matcher, Element::Keyword),
))(remaining) ))(remaining)
{ {
@@ -116,19 +127,29 @@ fn _element<'r, 's>(
Ok((remaining, element)) Ok((remaining, element))
} }
pub const fn detect_element( pub(crate) const fn detect_element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> { ) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()>
move |context: Context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph) {
move |context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _detect_element<'r, 's>( fn _detect_element<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(context, input).is_ok() { if alt((
detect_plain_list,
detect_footnote_definition,
detect_diary_sexp,
detect_comment,
detect_fixed_width_area,
detect_table,
))(input)
.is_ok()
{
return Ok((input, ())); return Ok((input, ()));
} }
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {

View File

@@ -2,18 +2,18 @@ use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::satisfy; use nom::character::complete::satisfy;
use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object::Entity;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::Entity;
// TODO: Make this a user-provided variable corresponding to elisp's org-entities // TODO: Make this a user-provided variable corresponding to elisp's org-entities
const ORG_ENTITIES: [&'static str; 413] = [ const ORG_ENTITIES: [&'static str; 413] = [
@@ -433,14 +433,15 @@ const ORG_ENTITIES: [&'static str; 413] = [
]; ];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn entity<'r, 's>( pub(crate) fn entity<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Entity<'s>> { ) -> Res<OrgSource<'s>, Entity<'s>> {
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
let (remaining, entity_name) = name(context, remaining)?; let (remaining, entity_name) = name(context, remaining)?;
let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?; let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -453,8 +454,8 @@ pub fn entity<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'r, 's>( fn name<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-entities and optionally org-entities-user // TODO: This should be defined by org-entities and optionally org-entities-user

View File

@@ -1,20 +0,0 @@
#[derive(Debug, Copy, Clone)]
pub enum ExitClass {
/// Headlines and sections.
Document = 1,
/// Elements who take priority over beta elements when matching.
Alpha = 20,
/// Elements who cede priority to alpha elements when matching.
Beta = 300,
/// Elements who cede priority to alpha and beta elements when matching.
Gamma = 4000,
}
impl std::fmt::Display for ExitClass {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

View File

@@ -8,33 +8,36 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_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::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::ExportSnippet; use crate::types::ExportSnippet;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_snippet<'r, 's>( pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportSnippet<'s>> { ) -> Res<OrgSource<'s>, ExportSnippet<'s>> {
let (remaining, _) = tag("@@")(input)?; let (remaining, _) = tag("@@")(input)?;
let (remaining, backend_name) = backend(context, remaining)?; let (remaining, backend_name) = backend(context, remaining)?;
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &export_snippet_end, exit_matcher: &export_snippet_end,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, backend_contents) = opt(tuple(( let (remaining, backend_contents) = opt(tuple((
tag(":"), tag(":"),
parser_with_context!(contents)(&parser_context), parser_with_context!(contents)(&parser_context),
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?; let (remaining, _) = tag("@@")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -47,8 +50,8 @@ pub fn export_snippet<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn backend<'r, 's>( fn backend<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, backend_name) = let (remaining, backend_name) =
@@ -58,8 +61,8 @@ fn backend<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn contents<'r, 's>( fn contents<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(verify( let (remaining, source) = recognize(verify(
@@ -70,8 +73,8 @@ fn contents<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn export_snippet_end<'r, 's>( fn export_snippet_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag("@@")(input) tag("@@")(input)

View File

@@ -12,17 +12,17 @@ use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::FixedWidthArea; use crate::types::FixedWidthArea;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fixed_width_area<'r, 's>( pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> { ) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context); let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
@@ -41,13 +41,13 @@ pub fn fixed_width_area<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'r, 's>( fn fixed_width_area_line<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _indent) = space0(input)?; let (remaining, _indent) = space0(input)?;
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple(( let (remaining, (_colon, _leading_whitespace_and_content, _line_ending)) = tuple((
tag(":"), tag(":"),
opt(tuple((space1, is_not("\r\n")))), opt(tuple((space1, is_not("\r\n")))),
alt((line_ending, eof)), alt((line_ending, eof)),
@@ -55,3 +55,14 @@ fn fixed_width_area_line<'r, 's>(
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((
start_of_line,
space0,
tag(":"),
alt((tag(" "), line_ending, eof)),
))(input)?;
Ok((input, ()))
}

View File

@@ -4,34 +4,37 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while; use nom::bytes::complete::take_while;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::include_input;
use super::util::WORD_CONSTITUENT_CHARACTERS; use super::util::WORD_CONSTITUENT_CHARACTERS;
use super::Context; use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::FootnoteDefinition;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
use crate::parser::util::maybe_consume_trailing_whitespace; use crate::parser::util::maybe_consume_trailing_whitespace;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::FootnoteDefinition;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_definition<'r, 's>( pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> { ) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
if immediate_in_section(context, "footnote definition") { if immediate_in_section(context, "footnote definition") {
@@ -41,20 +44,42 @@ pub fn footnote_definition<'r, 's>(
} }
start_of_line(input)?; start_of_line(input)?;
// Cannot be indented. // Cannot be indented.
let (remaining, (_lead_in, lbl, _lead_out, _ws)) = let (remaining, (_, lbl, _, _, _)) = tuple((
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?; tag_no_case("[fn:"),
let parser_context = context label,
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) tag("]"),
.with_additional_node(ContextElement::Context("footnote definition")) space0,
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { opt(verify(many0(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() <= 2
})),
))(input)?;
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("footnote definition"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &footnote_definition_end, exit_matcher: &footnote_definition_end,
})); }),
// TODO: The problem is we are not accounting for trailing whitespace like we do in section. Maybe it would be easier if we passed down whether or not to parse trailing whitespace into the element matcher similar to how tag takes in parameters. ];
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 element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) = let (mut remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(include_input(element_matcher), exit_matcher)(remaining)?;
// Re-parse the last element of the footnote definition with consume trailing whitespace off because the trailing whitespace needs to belong to the footnote definition, not the contents.
if context.should_consume_trailing_whitespace() {
if let Some((final_item_input, _)) = children.pop() {
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (remain, reparsed_final_item) =
parser_with_context!(element(true))(&final_item_context)(final_item_input)?;
children.push((final_item_input, reparsed_final_item));
remaining = remain;
}
}
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -62,13 +87,13 @@ pub fn footnote_definition<'r, 's>(
FootnoteDefinition { FootnoteDefinition {
source: source.into(), source: source.into(),
label: lbl.into(), label: lbl.into(),
children, children: children.into_iter().map(|(_, item)| item).collect(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { pub(crate) fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
digit1, digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)), take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
@@ -76,25 +101,14 @@ pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'r, 's>( fn footnote_definition_end<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let allow_nesting_context =
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
if immediate_in_section(context, "footnote definition") {
&allow_nesting_context
} else {
context
},
);
let maybe_consume_trailing_whitespace_matcher =
parser_with_context!(maybe_consume_trailing_whitespace)(context);
let (remaining, source) = alt(( let (remaining, source) = alt((
recognize(tuple(( recognize(tuple((
maybe_consume_trailing_whitespace_matcher, parser_with_context!(maybe_consume_trailing_whitespace)(context),
footnote_definition_matcher, detect_footnote_definition,
))), ))),
recognize(tuple(( recognize(tuple((
start_of_line, start_of_line,
@@ -107,12 +121,19 @@ fn footnote_definition_end<'r, 's>(
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::parser::parser_context::ContextTree; use crate::context::Context;
use crate::parser::parser_with_context::parser_with_context; use crate::context::GlobalSettings;
use crate::parser::Source; use crate::context::List;
use crate::types::Source;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
@@ -123,7 +144,9 @@ mod tests {
line footnote.", line footnote.",
); );
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context); let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) = let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");
@@ -154,7 +177,9 @@ line footnote.
not in the footnote.", not in the footnote.",
); );
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context); let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) = let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");

View File

@@ -1,29 +1,30 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::space0;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_context::ContextElement; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; 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::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::footnote_definition::label; use crate::parser::footnote_definition::label;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::FootnoteReference; use crate::types::FootnoteReference;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_reference<'r, 's>( pub(crate) fn footnote_reference<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
alt(( alt((
@@ -34,17 +35,17 @@ pub fn footnote_reference<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn anonymous_footnote<'r, 's>( fn anonymous_footnote<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn::")(input)?; let (remaining, _) = tag_no_case("[fn::")(input)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth()); let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma,
class: ExitClass::Beta,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
@@ -54,7 +55,8 @@ fn anonymous_footnote<'r, 's>(
)(remaining)?; )(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -67,19 +69,19 @@ fn anonymous_footnote<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inline_footnote<'r, 's>( fn inline_footnote<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?; let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?; let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag(":")(remaining)?; let (remaining, _) = tag(":")(remaining)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth()); let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma,
class: ExitClass::Beta,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
@@ -89,7 +91,8 @@ fn inline_footnote<'r, 's>(
)(remaining)?; )(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -102,14 +105,15 @@ fn inline_footnote<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_reference_only<'r, 's>( fn footnote_reference_only<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?; let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?; let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -121,17 +125,15 @@ fn footnote_reference_only<'r, 's>(
)) ))
} }
fn footnote_definition_end( fn footnote_definition_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
starting_bracket_depth: BracketDepth, move |context, input: OrgSource<'_>| {
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_footnote_definition_end(context, input, starting_bracket_depth) _footnote_definition_end(context, input, starting_bracket_depth)
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _footnote_definition_end<'r, 's>( fn _footnote_definition_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {

View File

@@ -12,28 +12,29 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::in_section;
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::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::GreaterBlock;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Element; use crate::types::Element;
use crate::parser::Paragraph; use crate::types::GreaterBlock;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn greater_block<'r, 's>( pub(crate) fn greater_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, GreaterBlock<'s>> { ) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
// TODO: Do I need to differentiate between different greater block types. // TODO: Do I need to differentiate between different greater block types.
@@ -49,11 +50,11 @@ pub fn greater_block<'r, 's>(
}), }),
))(remaining)?; ))(remaining)?;
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() { let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
"center" => "center block", "center" => "center block".to_owned(),
"quote" => "quote block", "quote" => "quote block".to_owned(),
_ => "greater block", name @ _ => format!("special block {}", name),
}; };
if immediate_in_section(context, context_name) { if in_section(context, context_name.as_str()) {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
@@ -61,13 +62,17 @@ pub fn greater_block<'r, 's>(
let exit_with_name = greater_block_end(name.into()); let exit_with_name = greater_block_end(name.into());
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context(context_name)) ContextElement::Context(context_name.as_str()),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &exit_with_name, exit_matcher: &exit_with_name,
})); }),
];
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 { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -120,19 +125,16 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
fn greater_block_end<'x>( fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
name: &'x str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: Can this be done without making an owned copy? // TODO: Can this be done without making an owned copy?
let name = name.to_owned(); move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
move |context: Context, input: OrgSource<'_>| _greater_block_end(context, input, name.as_str())
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _greater_block_end<'r, 's, 'x>( fn _greater_block_end<'b, 'g, 'r, 's, 'c>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
name: &'x str, name: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;

258
src/parser/headline.rs Normal file
View File

@@ -0,0 +1,258 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
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::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many1_count;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::section::section;
use super::util::get_consumed;
use super::util::start_of_line;
use crate::context::parser_with_context;
use crate::context::ContextElement;
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;
use crate::types::DocumentElement;
use crate::types::Heading;
use crate::types::Object;
use crate::types::PriorityCookie;
use crate::types::TodoKeywordType;
pub(crate) const fn heading(
parent_stars: usize,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> {
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (
remaining,
(star_count, maybe_todo_keyword, maybe_priority, maybe_comment, title, heading_tags),
) = headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let is_archived = heading_tags.contains(&"ARCHIVE");
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
priority_cookie: maybe_priority.map(|(priority, _)| priority),
title,
tags: heading_tags,
children,
is_comment: maybe_comment.is_some(),
is_archived,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
Option<(PriorityCookie, OrgSource<'s>)>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (
remaining,
(
_,
star_count,
_,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags,
_,
_,
),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
opt(tuple((priority_cookie, space1))),
opt(tuple((tag("COMMENT"), space1))),
many1(parser_with_context!(standard_set_object)(&parser_context)),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
let global_settings = context.get_global_settings();
if global_settings.in_progress_todo_keywords.is_empty()
&& global_settings.complete_todo_keywords.is_empty()
{
alt((
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
))(input)
} else {
for todo_keyword in global_settings
.in_progress_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
Err(_) => {}
}
}
for todo_keyword in global_settings
.complete_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoTodoKeyword".into(),
))))
}
}
fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCookie> {
let (remaining, (_, priority_character, _)) = tuple((
tag("[#"),
verify(anychar, |c| c.is_alphanumeric()),
tag("]"),
))(input)?;
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
nom::Err::Error(CustomError::MyError(MyError(
"Failed to cast priority cookie to number.".into(),
)))
})?;
Ok((remaining, cookie))
}

View File

@@ -9,14 +9,14 @@ use nom::multi::many1_count;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::HorizontalRule; use crate::types::HorizontalRule;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn horizontal_rule<'r, 's>( pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, HorizontalRule<'s>> { ) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
start_of_line(input)?; start_of_line(input)?;

View File

@@ -0,0 +1,69 @@
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::combinator::map;
use nom::multi::many0;
use nom::multi::many_till;
use super::keyword::filtered_keyword;
use super::keyword_todo::todo_keywords;
use super::OrgSource;
use crate::error::Res;
use crate::types::Keyword;
use crate::GlobalSettings;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn scan_for_in_buffer_settings<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
// TODO: Optimization idea: since this is slicing the OrgSource at each character, it might be more efficient to do a parser that uses a search function like take_until, and wrap it in a function similar to consumed but returning the input along with the normal output, then pass all of that into a verify that confirms we were at the start of a line using the input we just returned.
let keywords = many0(map(
many_till(anychar, filtered_keyword(in_buffer_settings_key)),
|(_, kw)| kw,
))(input);
keywords
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
tag_no_case("archive"),
tag_no_case("category"),
tag_no_case("columns"),
tag_no_case("filetags"),
tag_no_case("link"),
tag_no_case("priorities"),
tag_no_case("property"),
tag_no_case("seq_todo"),
tag_no_case("setupfile"),
tag_no_case("startup"),
tag_no_case("tags"),
tag_no_case("todo"),
tag_no_case("typ_todo"),
))(input)
}
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, String> {
let mut new_settings = original_settings.clone();
for kw in keywords.iter().filter(|kw| {
kw.key.eq_ignore_ascii_case("todo")
|| kw.key.eq_ignore_ascii_case("seq_todo")
|| kw.key.eq_ignore_ascii_case("typ_todo")
}) {
let (_, (in_progress_words, complete_words)) =
todo_keywords(kw.value).map_err(|err| err.to_string())?;
new_settings
.in_progress_todo_keywords
.extend(in_progress_words.into_iter().map(str::to_string));
new_settings
.complete_todo_keywords
.extend(complete_words.into_iter().map(str::to_string));
}
Ok(new_settings)
}

View File

@@ -4,7 +4,6 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -12,21 +11,23 @@ use nom::multi::many_till;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_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::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::InlineBabelCall; use crate::types::InlineBabelCall;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_babel_call<'r, 's>( pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, InlineBabelCall<'s>> { ) -> Res<OrgSource<'s>, InlineBabelCall<'s>> {
let (remaining, _) = tag_no_case("call_")(input)?; let (remaining, _) = tag_no_case("call_")(input)?;
@@ -34,7 +35,8 @@ pub fn inline_babel_call<'r, 's>(
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _argument) = argument(context, remaining)?; let (remaining, _argument) = argument(context, remaining)?;
let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -45,15 +47,15 @@ pub fn inline_babel_call<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'r, 's>( fn name<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &name_end, exit_matcher: &name_end,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
verify(anychar, |c| !(c.is_whitespace() || "[]()".contains(*c))), verify(anychar, |c| !(c.is_whitespace() || "[]()".contains(*c))),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
@@ -62,26 +64,26 @@ fn name<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name_end<'r, 's>( fn name_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of("[("))(input) recognize(one_of("[("))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn header<'r, 's>( fn header<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let exit_with_depth = header_end(remaining.get_bracket_depth()); let exit_with_depth = header_end(remaining.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
anychar, anychar,
@@ -91,17 +93,13 @@ fn header<'r, 's>(
Ok((remaining, name)) Ok((remaining, name))
} }
fn header_end( fn header_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
starting_bracket_depth: BracketDepth, move |context, input: OrgSource<'_>| _header_end(context, input, starting_bracket_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_header_end(context, input, starting_bracket_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _header_end<'r, 's>( fn _header_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -120,18 +118,18 @@ fn _header_end<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn argument<'r, 's>( fn argument<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let exit_with_depth = argument_end(remaining.get_parenthesis_depth()); let exit_with_depth = argument_end(remaining.get_parenthesis_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, name) = recognize(many_till( let (remaining, name) = recognize(many_till(
anychar, anychar,
@@ -141,17 +139,13 @@ fn argument<'r, 's>(
Ok((remaining, name)) Ok((remaining, name))
} }
fn argument_end( fn argument_end(starting_parenthesis_depth: BracketDepth) -> impl ContextMatcher {
starting_parenthesis_depth: BracketDepth, move |context, input: OrgSource<'_>| _argument_end(context, input, starting_parenthesis_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_argument_end(context, input, starting_parenthesis_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _argument_end<'r, 's>( fn _argument_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_parenthesis_depth: BracketDepth, starting_parenthesis_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {

View File

@@ -4,7 +4,6 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -14,28 +13,31 @@ use tracing::span;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_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::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::InlineSourceBlock; use crate::types::InlineSourceBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_source_block<'r, 's>( pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> { ) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> {
let (remaining, _) = tag_no_case("src_")(input)?; let (remaining, _) = tag_no_case("src_")(input)?;
let (remaining, _) = lang(context, remaining)?; let (remaining, _) = lang(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _body) = body(context, remaining)?; let (remaining, _body) = body(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -46,15 +48,15 @@ pub fn inline_source_block<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn lang<'r, 's>( fn lang<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &lang_end, exit_matcher: &lang_end,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, lang) = recognize(many_till( let (remaining, lang) = recognize(many_till(
verify(anychar, |c| !(c.is_whitespace() || "[{".contains(*c))), verify(anychar, |c| !(c.is_whitespace() || "[{".contains(*c))),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
@@ -63,26 +65,26 @@ fn lang<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn lang_end<'r, 's>( fn lang_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of("[{"))(input) recognize(one_of("[{"))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn header<'r, 's>( fn header<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let exit_with_depth = header_end(remaining.get_bracket_depth()); let exit_with_depth = header_end(remaining.get_bracket_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, header_contents) = recognize(many_till( let (remaining, header_contents) = recognize(many_till(
anychar, anychar,
@@ -92,17 +94,13 @@ fn header<'r, 's>(
Ok((remaining, header_contents)) Ok((remaining, header_contents))
} }
fn header_end( fn header_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
starting_bracket_depth: BracketDepth, move |context, input: OrgSource<'_>| _header_end(context, input, starting_bracket_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_header_end(context, input, starting_bracket_depth)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _header_end<'r, 's>( fn _header_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: BracketDepth, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -121,18 +119,18 @@ fn _header_end<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn body<'r, 's>( fn body<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("{")(input)?; let (remaining, _) = tag("{")(input)?;
let exit_with_depth = body_end(remaining.get_brace_depth()); let exit_with_depth = body_end(remaining.get_brace_depth());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, body_contents) = recognize(many_till( let (remaining, body_contents) = recognize(many_till(
anychar, anychar,
@@ -152,15 +150,13 @@ fn body<'r, 's>(
Ok((remaining, body_contents)) Ok((remaining, body_contents))
} }
fn body_end( fn body_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher {
starting_brace_depth: BracketDepth, move |context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _body_end<'r, 's>( fn _body_end<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_brace_depth: BracketDepth, starting_brace_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {

View File

@@ -5,8 +5,9 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while1; use nom::bytes::complete::take_while1;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::peek; use nom::combinator::peek;
@@ -16,66 +17,124 @@ use nom::sequence::tuple;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use crate::context::Matcher;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Keyword; use crate::types::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [ const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
"caption", "data", "header", "headers", "label", "name", "plot", "resname", "result", "caption", "data", "headers", "header", "label", "name", "plot", "resname", "results",
"results", "source", "srcname", "tblname", "result", "source", "srcname", "tblname",
]; ];
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"]; const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn filtered_keyword<F: Matcher>(
pub fn keyword<'r, 's>( key_parser: F,
_context: Context<'r, 's>, ) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
move |input| _filtered_keyword(&key_parser, input)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(key_parser))
)]
fn _filtered_keyword<'s, F: Matcher>(
key_parser: F,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
start_of_line(input)?; start_of_line(input)?;
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references. // TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
let (remaining, rule) = recognize(tuple(( let (remaining, (consumed_input, (_, _, parsed_key, _))) =
space0, consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?;
tag("#+"), match tuple((
not(peek(tag_no_case("call"))), space0::<OrgSource<'_>, CustomError<OrgSource<'_>>>,
not(peek(tag_no_case("begin"))),
is_not(" \t\r\n:"),
tag(":"),
alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
alt((line_ending, eof)), alt((line_ending, eof)),
)))(input)?; ))(remaining)
{
Ok((remaining, _)) => {
return Ok((
remaining,
Keyword {
source: consumed_input.into(),
key: parsed_key.into(),
value: "".into(),
},
));
}
Err(_) => {}
};
let (remaining, _ws) = space0(remaining)?;
let (remaining, parsed_value) = recognize(many_till(
anychar,
peek(tuple((space0, alt((line_ending, eof))))),
))(remaining)?;
let (remaining, _ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
Ok(( Ok((
remaining, remaining,
Keyword { Keyword {
source: rule.into(), source: consumed_input.into(),
key: parsed_key.into(),
value: parsed_value.into(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn affiliated_keyword<'r, 's>( pub(crate) fn keyword<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
start_of_line(input)?; filtered_keyword(regular_keyword_key)(input)
}
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references. #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
let (remaining, rule) = recognize(tuple(( pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
space0, _context: RefContext<'b, 'g, 'r, 's>,
tag("#+"), input: OrgSource<'s>,
affiliated_key, ) -> Res<OrgSource<'s>, Keyword<'s>> {
tag(":"), filtered_keyword(affiliated_key)(input)
alt((recognize(tuple((space1, is_not("\r\n")))), space0)), }
alt((line_ending, eof)),
)))(input)?; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
Ok(( pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
remaining, _context: RefContext<'b, 'g, 'r, 's>,
Keyword { input: OrgSource<'s>,
source: rule.into(), ) -> Res<OrgSource<'s>, Keyword<'s>> {
}, filtered_keyword(babel_call_key)(input)
)) }
#[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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(table_formula_key)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_formula_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag_no_case("tblfm")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
not(peek(alt((tag_no_case("call"), tag_no_case("begin")))))(input)?;
recognize(many_till(
anychar,
peek(alt((
recognize(one_of(" \t\r\n")), // Give up if we hit whitespace
recognize(tuple((tag(":"), one_of(" \t\r\n")))), // Stop if we see a colon followed by whitespace
recognize(tuple((tag(":"), is_not(" \t\r\n:"), not(tag(":"))))), // Stop if we see a colon that is the last colon before whitespace. This is for keywords like "#+foo:bar:baz: lorem: ipsum" which would have the key "foo:bar:baz".
))),
))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -0,0 +1,94 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_till;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::verify;
use nom::multi::separated_list0;
use nom::sequence::tuple;
use crate::error::Res;
// ref https://orgmode.org/manual/Per_002dfile-keywords.html
// ref https://orgmode.org/manual/Workflow-states.html
// Case is significant.
/// Parses the text in the value of a #+TODO keyword.
///
/// Example input: "foo bar baz | lorem ipsum"
pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
let (remaining, after_pipe_words) = opt(tuple((
tuple((space0, tag("|"), space0)),
separated_list0(space1, todo_keyword_word),
)))(remaining)?;
let (remaining, _eol) = alt((line_ending, eof))(remaining)?;
if let Some((_pipe, after_pipe_words)) = after_pipe_words {
Ok((remaining, (before_pipe_words, after_pipe_words)))
} else if !before_pipe_words.is_empty() {
// If there was no pipe, then the last word becomes a completion state instead.
let mut after_pipe_words = Vec::with_capacity(1);
after_pipe_words.push(
before_pipe_words
.pop()
.expect("If-statement proves this is Some."),
);
Ok((remaining, (before_pipe_words, after_pipe_words)))
} else {
// No words founds
Ok((remaining, (Vec::new(), Vec::new())))
}
}
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
verify(take_till(|c| " \t\r\n|".contains(c)), |result: &str| {
!result.is_empty()
})(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn before_and_after() -> Result<(), Box<dyn std::error::Error>> {
let input = "foo bar baz | lorem ipsum";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, vec!["foo", "bar", "baz"]);
assert_eq!(after_pipe_words, vec!["lorem", "ipsum"]);
Ok(())
}
#[test]
fn no_pipe() -> Result<(), Box<dyn std::error::Error>> {
let input = "foo bar baz";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, vec!["foo", "bar"]);
assert_eq!(after_pipe_words, vec!["baz"]);
Ok(())
}
#[test]
fn early_pipe() -> Result<(), Box<dyn std::error::Error>> {
let input = "| foo bar baz";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, Vec::<&str>::new());
assert_eq!(after_pipe_words, vec!["foo", "bar", "baz"]);
Ok(())
}
#[test]
fn late_pipe() -> Result<(), Box<dyn std::error::Error>> {
let input = "foo bar baz |";
let (remaining, (before_pipe_words, after_pipe_words)) = todo_keywords(input)?;
assert_eq!(remaining, "");
assert_eq!(before_pipe_words, vec!["foo", "bar", "baz"]);
assert_eq!(after_pipe_words, Vec::<&str>::new());
Ok(())
}
}

View File

@@ -13,19 +13,20 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::Context; 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::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::LatexEnvironment; use crate::types::LatexEnvironment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_environment<'r, 's>( pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> { ) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -39,13 +40,13 @@ pub fn latex_environment<'r, 's>(
))(remaining)?; ))(remaining)?;
let latex_environment_end_specialized = latex_environment_end(name.into()); let latex_environment_end_specialized = latex_environment_end(name.into());
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &latex_environment_end_specialized, exit_matcher: &latex_environment_end_specialized,
})); });
let parser_context = context.with_additional_node(&parser_context);
let (remaining, _contents) = contents(&latex_environment_end_specialized, context, remaining)?; let (remaining, _contents) = contents(&latex_environment_end_specialized)(context, remaining)?;
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?; let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -62,44 +63,42 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input) take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
} }
fn contents<F: ContextMatcher>(end_matcher: F) -> impl ContextMatcher {
move |context, input| _contents(&end_matcher, context, input)
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(end_matcher)) tracing::instrument(ret, level = "debug", skip(end_matcher))
)] )]
pub fn contents< fn _contents<'b, 'g, 'r, 's, F: ContextMatcher>(
'r,
's,
F: Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>,
>(
end_matcher: F, end_matcher: F,
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(many_till( let (remaining, source) = recognize(many_till(
anychar, anychar,
peek(alt(( peek(alt((
parser_with_context!(exit_matcher_parser)(context), parser_with_context!(exit_matcher_parser)(context),
parser_with_context!(end_matcher)(context), parser_with_context!(&end_matcher)(context),
))), ))),
))(input)?; ))(input)?;
Ok((remaining, source)) Ok((remaining, source))
} }
fn latex_environment_end( fn latex_environment_end(current_name: &str) -> impl ContextMatcher {
current_name: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_name_lower = current_name.to_lowercase(); let current_name_lower = current_name.to_lowercase();
move |context: Context, input: OrgSource<'_>| { move |context, input: OrgSource<'_>| {
_latex_environment_end(context, input, current_name_lower.as_str()) _latex_environment_end(context, input, current_name_lower.as_str())
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _latex_environment_end<'r, 's, 'x>( fn _latex_environment_end<'b, 'g, 'r, 's, 'c>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
current_name_lower: &'x str, current_name_lower: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;

View File

@@ -5,7 +5,6 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::none_of; use nom::character::complete::none_of;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -14,18 +13,19 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::LatexFragment; use crate::types::LatexFragment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_fragment<'r, 's>( pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexFragment<'s>> { ) -> Res<OrgSource<'s>, LatexFragment<'s>> {
let (remaining, _) = alt(( let (remaining, _) = alt((
@@ -36,7 +36,8 @@ pub fn latex_fragment<'r, 's>(
parser_with_context!(dollar_char_fragment)(context), parser_with_context!(dollar_char_fragment)(context),
parser_with_context!(bordered_dollar_fragment)(context), parser_with_context!(bordered_dollar_fragment)(context),
))(input)?; ))(input)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -47,8 +48,8 @@ pub fn latex_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn raw_latex_fragment<'r, 's>( fn raw_latex_fragment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
@@ -60,16 +61,16 @@ fn raw_latex_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'r, 's>( fn name<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
alpha1(input) alpha1(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn brackets<'r, 's>( fn brackets<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, body) = alt(( let (remaining, body) = alt((
@@ -100,8 +101,8 @@ fn brackets<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_parenthesis_fragment<'r, 's>( fn escaped_parenthesis_fragment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\(")(input)?; let (remaining, _) = tag("\\(")(input)?;
@@ -119,8 +120,8 @@ fn escaped_parenthesis_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn escaped_bracket_fragment<'r, 's>( fn escaped_bracket_fragment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tag("\\[")(input)?; let (remaining, _) = tag("\\[")(input)?;
@@ -138,8 +139,8 @@ fn escaped_bracket_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn double_dollar_fragment<'r, 's>( fn double_dollar_fragment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$? // TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$?
@@ -158,8 +159,8 @@ fn double_dollar_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dollar_char_fragment<'r, 's>( fn dollar_char_fragment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (_, _) = pre(context, input)?; let (_, _) = pre(context, input)?;
@@ -173,7 +174,10 @@ fn dollar_char_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { fn pre<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();
if let Some('$') = preceding_character { if let Some('$') = preceding_character {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
@@ -184,7 +188,10 @@ pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSo
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { fn post<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
// TODO: What about eof? Test to find out. // TODO: What about eof? Test to find out.
// TODO: Figure out which punctuation characters should be included. // TODO: Figure out which punctuation characters should be included.
@@ -193,8 +200,8 @@ pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgS
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bordered_dollar_fragment<'r, 's>( fn bordered_dollar_fragment<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (_, _) = pre(context, input)?; let (_, _) = pre(context, input)?;
@@ -219,16 +226,16 @@ fn bordered_dollar_fragment<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn open_border<'r, 's>( fn open_border<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input) recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn close_border<'r, 's>( fn close_border<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();

View File

@@ -7,47 +7,53 @@ use nom::character::complete::space1;
use nom::combinator::consumed; use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; 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::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::lesser_element::CommentBlock;
use crate::parser::lesser_element::ExampleBlock;
use crate::parser::lesser_element::ExportBlock;
use crate::parser::lesser_element::SrcBlock;
use crate::parser::lesser_element::VerseBlock;
use crate::parser::object::Object;
use crate::parser::object::PlainText;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::util::text_until_exit; use crate::parser::util::text_until_exit;
use crate::types::CommentBlock;
use crate::types::ExampleBlock;
use crate::types::ExportBlock;
use crate::types::Object;
use crate::types::PlainText;
use crate::types::SrcBlock;
use crate::types::VerseBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verse_block<'r, 's>( pub(crate) fn verse_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, VerseBlock<'s>> { ) -> Res<OrgSource<'s>, VerseBlock<'s>> {
let (remaining, name) = lesser_block_begin("verse")(context, input)?; let (remaining, name) = lesser_block_begin("verse")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("verse"); let lesser_block_end_specialized = lesser_block_end("verse");
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context("lesser block")) ContextElement::Context("lesser block"),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Alpha,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
})); }),
];
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 { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -84,21 +90,25 @@ pub fn verse_block<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment_block<'r, 's>( pub(crate) fn comment_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CommentBlock<'s>> { ) -> Res<OrgSource<'s>, CommentBlock<'s>> {
let (remaining, name) = lesser_block_begin("comment")(context, input)?; let (remaining, name) = lesser_block_begin("comment")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("comment"); let lesser_block_end_specialized = lesser_block_end("comment");
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context("lesser block")) ContextElement::Context("lesser block"),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Alpha,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
})); }),
];
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 { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -120,21 +130,25 @@ pub fn comment_block<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn example_block<'r, 's>( pub(crate) fn example_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExampleBlock<'s>> { ) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
let (remaining, _name) = lesser_block_begin("example")(context, input)?; let (remaining, _name) = lesser_block_begin("example")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("example"); let lesser_block_end_specialized = lesser_block_end("example");
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context("lesser block")) ContextElement::Context("lesser block"),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Alpha,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
})); }),
];
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 { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -156,22 +170,26 @@ pub fn example_block<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_block<'r, 's>( pub(crate) fn export_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportBlock<'s>> { ) -> Res<OrgSource<'s>, ExportBlock<'s>> {
let (remaining, name) = lesser_block_begin("export")(context, input)?; let (remaining, name) = lesser_block_begin("export")(context, input)?;
// 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. // 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, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("export"); let lesser_block_end_specialized = lesser_block_end("export");
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context("lesser block")) ContextElement::Context("lesser block"),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Alpha,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
})); }),
];
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 { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -193,22 +211,26 @@ pub fn export_block<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn src_block<'r, 's>( pub(crate) fn src_block<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> { ) -> Res<OrgSource<'s>, SrcBlock<'s>> {
let (remaining, name) = lesser_block_begin("src")(context, input)?; let (remaining, name) = lesser_block_begin("src")(context, input)?;
// 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. // 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, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("src"); let lesser_block_end_specialized = lesser_block_end("src");
let parser_context = context let contexts = [
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) ContextElement::ConsumeTrailingWhitespace(true),
.with_additional_node(ContextElement::Context("lesser block")) ContextElement::Context("lesser block"),
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &lesser_block_end_specialized, exit_matcher: &lesser_block_end_specialized,
})); }),
];
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 { let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters), Some((_ws, parameters)) => Some(parameters),
None => None, None => None,
@@ -239,26 +261,25 @@ fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input) is_not("\r\n")(input)
} }
fn lesser_block_end( fn lesser_block_end(current_name: &str) -> impl ContextMatcher {
current_name: &str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_name_lower = current_name.to_lowercase(); let current_name_lower = current_name.to_lowercase();
move |context: Context, input: OrgSource<'_>| { move |context, input: OrgSource<'_>| {
_lesser_block_end(context, input, current_name_lower.as_str()) _lesser_block_end(context, input, current_name_lower.as_str())
} }
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _lesser_block_end<'r, 's, 'x>( fn _lesser_block_end<'b, 'g, 'r, 's, 'c>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
current_name_lower: &'x str, current_name_lower: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _ws)) = tuple(( let (remaining, (_begin, _name, _ws, _ending)) = tuple((
tag_no_case("#+end_"), tag_no_case("#+end_"),
tag_no_case(current_name_lower), tag_no_case(current_name_lower),
space0,
alt((eof, line_ending)), alt((eof, line_ending)),
))(remaining)?; ))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -268,18 +289,16 @@ fn _lesser_block_end<'r, 's, 'x>(
/// Parser for the beginning of a lesser block /// Parser for the beginning of a lesser block
/// ///
/// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn. /// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
const fn lesser_block_begin( const fn lesser_block_begin<'c>(current_name: &'c str) -> impl ContextMatcher + 'c {
current_name: &'static str,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time? // TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
move |context: Context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name) move |context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _lesser_block_begin<'r, 's, 'x>( fn _lesser_block_begin<'b, 'g, 'r, 's, 'c>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
current_name_lower: &'x str, current_name_lower: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;

View File

@@ -5,16 +5,16 @@ use nom::combinator::recognize;
use nom::multi::many0; use nom::multi::many0;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::LineBreak; use crate::types::LineBreak;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn line_break<'r, 's>( pub(crate) fn line_break<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LineBreak<'s>> { ) -> Res<OrgSource<'s>, LineBreak<'s>> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
@@ -31,7 +31,10 @@ pub fn line_break<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { fn pre<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();
match preceding_character { match preceding_character {
// If None, we are at the start of the file // If None, we are at the start of the file

View File

@@ -1,183 +0,0 @@
use std::fmt::Debug;
use std::rc::Rc;
#[derive(Debug)]
pub struct List<T> {
head: Option<Rc<Node<T>>>,
}
impl<T> Clone for List<T> {
fn clone(&self) -> Self {
List {
head: self.head.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct Node<T> {
data: T,
parent: Option<Rc<Node<T>>>,
}
impl<T> Node<T> {
pub fn get_data(&self) -> &T {
&self.data
}
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None }
}
pub fn branch_from(trunk: &Rc<Node<T>>) -> Self {
List {
head: Some(trunk.clone()),
}
}
pub fn push_front(&self, data: T) -> List<T> {
List {
head: Some(Rc::new(Node {
data: data,
parent: self.head.clone(),
})),
}
}
pub fn pop_front(&mut self) -> (Option<T>, List<T>) {
match self.head.take() {
None => (None, List::new()),
Some(popped_node) => {
let extracted_node = match Rc::try_unwrap(popped_node) {
Ok(node) => node,
Err(_) => panic!("try_unwrap failed on Rc in pop_front on List."),
};
(
Some(extracted_node.data),
List {
head: extracted_node.parent,
},
)
}
}
}
#[allow(dead_code)]
pub fn without_front(&self) -> List<T> {
List {
head: self.head.as_ref().map(|node| node.parent.clone()).flatten(),
}
}
#[allow(dead_code)]
pub fn get_data(&self) -> Option<&T> {
self.head.as_ref().map(|rc_node| &rc_node.data)
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.head.is_none()
}
pub fn ptr_eq(&self, other: &List<T>) -> bool {
match (self.head.as_ref(), other.head.as_ref()) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some(me), Some(them)) => Rc::ptr_eq(me, them),
}
}
pub fn iter(&self) -> impl Iterator<Item = &Rc<Node<T>>> {
NodeIter {
position: &self.head,
}
}
#[allow(dead_code)]
pub fn iter_until<'a>(&'a self, other: &'a List<T>) -> impl Iterator<Item = &Rc<Node<T>>> {
NodeIterUntil {
position: &self.head,
stop: &other.head,
}
}
#[allow(dead_code)]
pub fn into_iter_until<'a>(self, other: &'a List<T>) -> impl Iterator<Item = T> + 'a {
NodeIntoIterUntil {
position: self,
stop: &other,
}
}
}
pub struct NodeIter<'a, T> {
position: &'a Option<Rc<Node<T>>>,
}
impl<'a, T> Iterator for NodeIter<'a, T> {
type Item = &'a Rc<Node<T>>;
fn next(&mut self) -> Option<Self::Item> {
let (return_value, next_position) = match &self.position {
None => return None,
Some(rc_node) => {
let next_position = &rc_node.parent;
let return_value = rc_node;
(return_value, next_position)
}
};
self.position = next_position;
Some(return_value)
}
}
pub struct NodeIterUntil<'a, T> {
position: &'a Option<Rc<Node<T>>>,
stop: &'a Option<Rc<Node<T>>>,
}
impl<'a, T> Iterator for NodeIterUntil<'a, T> {
type Item = &'a Rc<Node<T>>;
fn next(&mut self) -> Option<Self::Item> {
match (self.position, self.stop) {
(_, None) => {}
(None, _) => {}
(Some(this_rc), Some(stop_rc)) => {
if Rc::ptr_eq(this_rc, stop_rc) {
return None;
}
}
};
let (return_value, next_position) = match &self.position {
None => return None,
Some(rc_node) => {
let next_position = &rc_node.parent;
let return_value = rc_node;
(return_value, next_position)
}
};
self.position = next_position;
Some(return_value)
}
}
pub struct NodeIntoIterUntil<'a, T> {
position: List<T>,
stop: &'a List<T>,
}
impl<'a, T> Iterator for NodeIntoIterUntil<'a, T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.position.ptr_eq(self.stop) {
return None;
}
let (popped_element, new_position) = self.position.pop_front();
self.position = new_position;
popped_element
}
}

View File

@@ -7,33 +7,28 @@ mod diary_sexp;
mod document; mod document;
mod drawer; mod drawer;
mod dynamic_block; mod dynamic_block;
mod element;
mod element_parser; mod element_parser;
mod entity; mod entity;
mod exiting;
mod export_snippet; mod export_snippet;
mod fixed_width_area; mod fixed_width_area;
mod footnote_definition; mod footnote_definition;
mod footnote_reference; mod footnote_reference;
mod greater_block; mod greater_block;
mod greater_element; mod headline;
mod horizontal_rule; mod horizontal_rule;
mod in_buffer_settings;
mod inline_babel_call; mod inline_babel_call;
mod inline_source_block; mod inline_source_block;
mod keyword; mod keyword;
mod keyword_todo;
mod latex_environment; mod latex_environment;
mod latex_fragment; mod latex_fragment;
mod lesser_block; mod lesser_block;
mod lesser_element;
mod line_break; mod line_break;
mod list;
mod object;
mod object_parser; mod object_parser;
mod org_macro; mod org_macro;
mod org_source; mod org_source;
mod paragraph; mod paragraph;
mod parser_context;
mod parser_with_context;
mod plain_link; mod plain_link;
mod plain_list; mod plain_list;
mod plain_text; mod plain_text;
@@ -41,8 +36,7 @@ mod planning;
mod property_drawer; mod property_drawer;
mod radio_link; mod radio_link;
mod regular_link; mod regular_link;
pub mod sexp; mod section;
mod source;
mod statistics_cookie; mod statistics_cookie;
mod subscript_and_superscript; mod subscript_and_superscript;
mod table; mod table;
@@ -51,63 +45,6 @@ mod text_markup;
mod timestamp; mod timestamp;
mod token; mod token;
mod util; mod util;
pub use document::document; pub use document::parse;
pub use document::Document; pub use document::parse_with_settings;
pub use document::DocumentElement; pub(crate) use org_source::OrgSource;
pub use document::Heading;
pub use document::Section;
pub use element::Element;
pub use greater_element::Drawer;
pub use greater_element::DynamicBlock;
pub use greater_element::FootnoteDefinition;
pub use greater_element::GreaterBlock;
pub use greater_element::PlainList;
pub use greater_element::PlainListItem;
pub use greater_element::PropertyDrawer;
pub use greater_element::Table;
pub use greater_element::TableRow;
pub use lesser_element::Clock;
pub use lesser_element::Comment;
pub use lesser_element::CommentBlock;
pub use lesser_element::DiarySexp;
pub use lesser_element::ExampleBlock;
pub use lesser_element::ExportBlock;
pub use lesser_element::FixedWidthArea;
pub use lesser_element::HorizontalRule;
pub use lesser_element::Keyword;
pub use lesser_element::LatexEnvironment;
pub use lesser_element::Paragraph;
pub use lesser_element::Planning;
pub use lesser_element::SrcBlock;
pub use lesser_element::TableCell;
pub use lesser_element::VerseBlock;
pub use object::AngleLink;
pub use object::Bold;
pub use object::Citation;
pub use object::CitationReference;
pub use object::Code;
pub use object::Entity;
pub use object::ExportSnippet;
pub use object::FootnoteReference;
pub use object::InlineBabelCall;
pub use object::InlineSourceBlock;
pub use object::Italic;
pub use object::LatexFragment;
pub use object::LineBreak;
pub use object::Object;
pub use object::OrgMacro;
pub use object::PlainLink;
pub use object::PlainText;
pub use object::RadioLink;
pub use object::RadioTarget;
pub use object::RegularLink;
pub use object::StatisticsCookie;
pub use object::StrikeThrough;
pub use object::Subscript;
pub use object::Superscript;
pub use object::Target;
pub use object::Timestamp;
pub use object::Underline;
pub use object::Verbatim;
pub use source::Source;
type Context<'r, 's> = &'r parser_context::ContextTree<'r, 's>;

View File

@@ -2,10 +2,13 @@ use nom::branch::alt;
use nom::combinator::map; use nom::combinator::map;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context;
use super::plain_text::plain_text; use super::plain_text::plain_text;
use super::regular_link::regular_link; use super::regular_link::regular_link;
use super::Context; use super::subscript_and_superscript::detect_subscript_or_superscript;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::angle_link::angle_link; use crate::parser::angle_link::angle_link;
use crate::parser::citation::citation; use crate::parser::citation::citation;
@@ -16,7 +19,6 @@ use crate::parser::inline_babel_call::inline_babel_call;
use crate::parser::inline_source_block::inline_source_block; use crate::parser::inline_source_block::inline_source_block;
use crate::parser::latex_fragment::latex_fragment; use crate::parser::latex_fragment::latex_fragment;
use crate::parser::line_break::line_break; use crate::parser::line_break::line_break;
use crate::parser::object::Object;
use crate::parser::org_macro::org_macro; use crate::parser::org_macro::org_macro;
use crate::parser::plain_link::plain_link; use crate::parser::plain_link::plain_link;
use crate::parser::radio_link::radio_link; use crate::parser::radio_link::radio_link;
@@ -27,90 +29,41 @@ use crate::parser::subscript_and_superscript::superscript;
use crate::parser::target::target; use crate::parser::target::target;
use crate::parser::text_markup::text_markup; use crate::parser::text_markup::text_markup;
use crate::parser::timestamp::timestamp; use crate::parser::timestamp::timestamp;
use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn standard_set_object<'r, 's>( pub(crate) fn standard_set_object<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp), parser_with_context!(standard_set_object_sans_plain_text)(context),
map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(plain_text(detect_standard_set_object_sans_plain_text))(context),
Object::Superscript, Object::PlainText,
), ),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(line_break)(context), Object::LineBreak),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink),
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
parser_with_context!(text_markup)(context),
map(
parser_with_context!(regular_link)(context),
Object::RegularLink,
),
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn minimal_set_object<'r, 's>( pub(crate) fn minimal_set_object<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript), parser_with_context!(minimal_set_object_sans_plain_text)(context),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(plain_text(detect_minimal_set_object_sans_plain_text))(context),
Object::Superscript, Object::PlainText,
), ),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn any_object_except_plain_text<'r, 's>( fn standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
@@ -166,8 +119,81 @@ pub fn any_object_except_plain_text<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_description_object_set<'r, 's>( fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if standard_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if minimal_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
let (remaining, object) = alt((
parser_with_context!(regular_link_description_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(
detect_regular_link_description_set_object_sans_plain_text
))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]] // TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
@@ -189,14 +215,46 @@ pub fn regular_link_description_object_set<'r, 's>(
Object::InlineBabelCall, Object::InlineBabelCall,
), ),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'r, 's>( fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if regular_link_description_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn table_cell_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
parser_with_context!(table_cell_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(detect_table_cell_set_object_sans_plain_text))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
@@ -223,7 +281,24 @@ pub fn table_cell_set_object<'r, 's>(
), ),
map(parser_with_context!(target)(context), Object::Target), map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if table_cell_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}

View File

@@ -1,6 +1,5 @@
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
@@ -9,23 +8,26 @@ use nom::multi::many0;
use nom::multi::separated_list0; use nom::multi::separated_list0;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object::OrgMacro;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::OrgMacro;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_macro<'r, 's>( pub(crate) fn org_macro<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgMacro<'s>> { ) -> Res<OrgSource<'s>, OrgMacro<'s>> {
let (remaining, _) = tag("{{{")(input)?; let (remaining, _) = tag("{{{")(input)?;
let (remaining, macro_name) = org_macro_name(context, remaining)?; let (remaining, macro_name) = org_macro_name(context, remaining)?;
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?; let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
let (remaining, _) = tag("}}}")(remaining)?; let (remaining, _) = tag("}}}")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -43,8 +45,8 @@ pub fn org_macro<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn org_macro_name<'r, 's>( fn org_macro_name<'b, 'g, 'r, 's>(
_context: Context<'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = verify(anychar, |c| c.is_alphabetic())(input)?; let (remaining, _) = verify(anychar, |c| c.is_alphabetic())(input)?;
@@ -56,8 +58,8 @@ fn org_macro_name<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn org_macro_args<'r, 's>( fn org_macro_args<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
@@ -69,8 +71,8 @@ fn org_macro_args<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn org_macro_arg<'r, 's>( fn org_macro_arg<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let mut remaining = input; let mut remaining = input;
@@ -86,6 +88,11 @@ fn org_macro_arg<'r, 's>(
} }
if next_char == '\\' { if next_char == '\\' {
escaping = true; escaping = true;
if peek(tag::<_, _, CustomError<_>>(")"))(new_remaining).is_ok() {
// Special case for backslash at the end of a macro
remaining = new_remaining;
break;
}
} }
if next_char == ',' || next_char == ')' { if next_char == ',' || next_char == ')' {
break; break;

View File

@@ -11,10 +11,10 @@ use nom::Slice;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
pub type BracketDepth = i16; pub(crate) type BracketDepth = i16;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct OrgSource<'s> { pub(crate) struct OrgSource<'s> {
full_source: &'s str, full_source: &'s str,
start: usize, start: usize,
end: usize, // exclusive end: usize, // exclusive
@@ -27,7 +27,7 @@ pub struct OrgSource<'s> {
impl<'s> std::fmt::Debug for OrgSource<'s> { impl<'s> std::fmt::Debug for OrgSource<'s> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("OrgSource") f.debug_tuple("Org")
.field(&Into::<&str>::into(self)) .field(&Into::<&str>::into(self))
.finish() .finish()
} }
@@ -37,7 +37,7 @@ impl<'s> OrgSource<'s> {
/// Returns a wrapped string that keeps track of values we need for parsing org-mode. /// Returns a wrapped string that keeps track of values we need for parsing org-mode.
/// ///
/// Only call this on the full original string. Calling this on a substring can result in invalid values. /// Only call this on the full original string. Calling this on a substring can result in invalid values.
pub fn new(input: &'s str) -> Self { pub(crate) fn new(input: &'s str) -> Self {
OrgSource { OrgSource {
full_source: input, full_source: input,
start: 0, start: 0,
@@ -51,37 +51,37 @@ impl<'s> OrgSource<'s> {
} }
/// Get the text since the line break preceding the start of this WrappedInput. /// Get the text since the line break preceding the start of this WrappedInput.
pub fn text_since_line_break(&self) -> &'s str { pub(crate) fn text_since_line_break(&self) -> &'s str {
&self.full_source[self.start_of_line..self.start] &self.full_source[self.start_of_line..self.start]
} }
pub fn len(&self) -> usize { pub(crate) fn len(&self) -> usize {
self.end - self.start self.end - self.start
} }
pub fn get_preceding_character(&self) -> Option<char> { pub(crate) fn get_preceding_character(&self) -> Option<char> {
self.preceding_character self.preceding_character
} }
pub fn is_at_start_of_line(&self) -> bool { pub(crate) fn is_at_start_of_line(&self) -> bool {
self.start == self.start_of_line self.start == self.start_of_line
} }
pub fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> { pub(crate) fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> {
assert!(other.start >= self.start); assert!(other.start >= self.start);
assert!(other.end <= self.end); assert!(other.end <= self.end);
self.slice(..(other.start - self.start)) self.slice(..(other.start - self.start))
} }
pub fn get_bracket_depth(&self) -> BracketDepth { pub(crate) fn get_bracket_depth(&self) -> BracketDepth {
self.bracket_depth self.bracket_depth
} }
pub fn get_brace_depth(&self) -> BracketDepth { pub(crate) fn get_brace_depth(&self) -> BracketDepth {
self.brace_depth self.brace_depth
} }
pub fn get_parenthesis_depth(&self) -> BracketDepth { pub(crate) fn get_parenthesis_depth(&self) -> BracketDepth {
self.parenthesis_depth self.parenthesis_depth
} }
} }
@@ -145,6 +145,9 @@ where
if new_end > self.end { if new_end > self.end {
panic!("Attempted to extend past the end of the WrappedInput.") panic!("Attempted to extend past the end of the WrappedInput.")
} }
if new_start == self.start && new_end == self.end {
return self.clone();
}
let skipped_text = &self.full_source[self.start..new_start]; let skipped_text = &self.full_source[self.start..new_start];
let mut start_of_line = self.start_of_line; let mut start_of_line = self.start_of_line;
@@ -183,7 +186,7 @@ where
start: new_start, start: new_start,
end: new_end, end: new_end,
start_of_line, start_of_line,
preceding_character: skipped_text.chars().last(), preceding_character: skipped_text.chars().last().or(self.preceding_character),
bracket_depth, bracket_depth,
brace_depth, brace_depth,
parenthesis_depth, parenthesis_depth,
@@ -303,7 +306,7 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
} }
} }
pub fn convert_error<'a, I: Into<CustomError<&'a str>>>( pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
err: nom::Err<I>, err: nom::Err<I>,
) -> nom::Err<CustomError<&'a str>> { ) -> nom::Err<CustomError<&'a str>> {
match err { match err {
@@ -318,6 +321,7 @@ impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
match value { match value {
CustomError::MyError(err) => CustomError::MyError(err.into()), CustomError::MyError(err) => CustomError::MyError(err.into()),
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind), CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
CustomError::IO(err) => CustomError::IO(err),
} }
} }
} }

View File

@@ -7,30 +7,30 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::element_parser::detect_element; use super::element_parser::detect_element;
use super::lesser_element::Paragraph;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::blank_line; use super::util::blank_line;
use super::util::get_consumed; use super::util::get_consumed;
use super::Context; use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::Paragraph;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn paragraph<'r, 's>( pub(crate) fn paragraph<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> { ) -> Res<OrgSource<'s>, Paragraph<'s>> {
let parser_context = let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &paragraph_end, exit_matcher: &paragraph_end,
})); });
let parser_context = context.with_additional_node(&parser_context);
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); 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 exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -53,8 +53,8 @@ pub fn paragraph<'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn paragraph_end<'r, 's>( fn paragraph_end<'b, 'g, 'r, 's>(
context: Context<'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let non_paragraph_element_matcher = parser_with_context!(detect_element(false))(context); let non_paragraph_element_matcher = parser_with_context!(detect_element(false))(context);
@@ -67,16 +67,21 @@ fn paragraph_end<'r, 's>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::context::parser_with_context;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::org_source::OrgSource; use crate::parser::org_source::OrgSource;
use crate::parser::parser_context::ContextTree; use crate::types::Source;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
let input = OrgSource::new("foo bar baz\n\nlorem ipsum"); let input = OrgSource::new("foo bar baz\n\nlorem ipsum");
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = parser_with_context!(element(true))(&initial_context); let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph"); let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let (remaining, second_paragraph) = let (remaining, second_paragraph) =

View File

@@ -1,139 +0,0 @@
use std::rc::Rc;
use nom::combinator::eof;
use nom::IResult;
use super::list::List;
use super::list::Node;
use super::org_source::OrgSource;
use super::Context;
use super::Object;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
type Matcher =
dyn for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
#[derive(Debug, Clone)]
pub struct ContextTree<'r, 's> {
tree: List<ContextElement<'r, 's>>,
}
impl<'r, 's> ContextTree<'r, 's> {
pub fn new() -> Self {
ContextTree { tree: List::new() }
}
pub fn branch_from(trunk: &Rc<Node<ContextElement<'r, 's>>>) -> Self {
ContextTree {
tree: List::branch_from(trunk),
}
}
#[allow(dead_code)]
pub fn ptr_eq<'x, 'y>(&self, other: &ContextTree<'x, 'y>) -> bool {
self.tree.ptr_eq(&other.tree)
}
pub fn with_additional_node(&self, data: ContextElement<'r, 's>) -> ContextTree<'r, 's> {
let new_list = self.tree.push_front(data);
ContextTree { tree: new_list }
}
pub fn iter(&self) -> impl Iterator<Item = &Rc<Node<ContextElement<'r, 's>>>> {
self.tree.iter()
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn check_exit_matcher(
&'r self,
i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
// Special check for EOF. We don't just make this a document-level exit matcher since the IgnoreParent ChainBehavior could cause early exit matchers to not run.
let at_end_of_file = eof(i);
if at_end_of_file.is_ok() {
return at_end_of_file;
}
// let blocked_context =
// self.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
// exit_matcher: ChainBehavior::IgnoreParent(Some(&always_fail)),
// }));
let mut current_class_filter = ExitClass::Gamma;
for current_node in self.iter() {
let context_element = current_node.get_data();
match context_element {
ContextElement::ExitMatcherNode(exit_matcher) => {
if exit_matcher.class as u32 <= current_class_filter as u32 {
current_class_filter = exit_matcher.class;
let local_context = ContextTree::branch_from(current_node);
let local_result = (exit_matcher.exit_matcher)(&local_context, i);
if local_result.is_ok() {
return local_result;
}
}
}
_ => {}
};
}
// TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError(
"NoExit".into(),
))));
}
/// Indicates if elements should consume the whitespace after them.
///
/// Defaults to true.
pub fn should_consume_trailing_whitespace(&self) -> bool {
self._should_consume_trailing_whitespace().unwrap_or(true)
}
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
for current_node in self.iter() {
let context_element = current_node.get_data();
match context_element {
ContextElement::ConsumeTrailingWhitespace(should) => {
return Some(*should);
}
_ => {}
}
}
None
}
}
#[derive(Debug)]
pub enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>),
/// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str),
/// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool),
/// The contents of a radio target.
///
/// If any are found, this will force a 2nd parse through the
/// org-mode document since text needs to be re-parsed to look for
/// radio links matching the contents of radio targets.
RadioTarget(Vec<&'r Vec<Object<'s>>>),
}
pub struct ExitMatcherNode<'r> {
pub exit_matcher: &'r Matcher,
pub class: ExitClass,
}
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ExitMatcherNode");
formatter.field("class", &self.class.to_string());
formatter.finish()
}
}

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