286 Commits

Author SHA1 Message Date
Tom Alexander
068864ea87 Publish version 0.1.14.
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2024-01-03 23:59:51 -05:00
Tom Alexander
03a3ddbd63 Merge branch 'wasm'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2024-01-03 23:56:35 -05:00
Tom Alexander
122adee23b Hide the wasm module.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2024-01-03 23:38:04 -05:00
Tom Alexander
556afecbb8 Hide the util module. 2024-01-03 23:04:47 -05:00
Tom Alexander
e4407cbdd1 Hide the event_count module.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
By placing the code for the parse executable inside a module inside the organic library, we only need to expose the entrypoint publicly rather than all functions it calls. This hides the event_count module, but I will be expanding the practice to the rest of the code base shortly. This is important for not inadvertently promising stability w.r.t. semver for essentially internal functions for development tools.

It was the parse binary, not compare.
2024-01-03 21:17:44 -05:00
Tom Alexander
f57d60dab0 Add a doc target to the Makefile. 2024-01-03 19:55:22 -05:00
Tom Alexander
0aa3939a75 Format. 2024-01-01 18:34:10 -05:00
Tom Alexander
52cb81e75e Cleanup.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-31 12:02:02 -05:00
Tom Alexander
945121202d Remove wasm_test's dependency on compare module.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-31 11:11:25 -05:00
Tom Alexander
f4e0dddd9d Fix clippy.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-30 23:14:40 -05:00
Tom Alexander
6b62176fd0 Run cargo fix.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-30 22:22:32 -05:00
Tom Alexander
44483b4d54 Break util up into modules. 2023-12-30 22:19:16 -05:00
Tom Alexander
48d3de77fe Move elisp fact to util. 2023-12-30 21:37:25 -05:00
Tom Alexander
680b176501 Fix src block value.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
2023-12-30 21:30:08 -05:00
Tom Alexander
dc0338e978 Handle nil in object tree. 2023-12-30 21:28:25 -05:00
Tom Alexander
ff3e0a50af Implement dynamic block.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-30 20:56:35 -05:00
Tom Alexander
03c8c07fe0 Implement quote block. 2023-12-30 20:53:20 -05:00
Tom Alexander
3a6fc5b669 Support noop on all token types. 2023-12-30 20:50:28 -05:00
Tom Alexander
d258cdb839 Support null vs noop comparison. 2023-12-30 20:47:17 -05:00
Tom Alexander
aa5629354e Implement special block. 2023-12-30 20:43:01 -05:00
Tom Alexander
efc4a04829 Implement center block. 2023-12-30 20:38:08 -05:00
Tom Alexander
dd611ea64a Fix plain list item. 2023-12-30 20:35:27 -05:00
Tom Alexander
4bd5f3bec7 Implement node property. 2023-12-30 19:01:07 -05:00
Tom Alexander
c2b3509b6a Implement property drawer. 2023-12-30 18:59:52 -05:00
Tom Alexander
7f3f5fb889 Implement table cell.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-30 18:52:52 -05:00
Tom Alexander
e0fbf17226 Implement table row. 2023-12-30 18:52:51 -05:00
Tom Alexander
4e18cbafba Implement table. 2023-12-30 18:52:51 -05:00
Tom Alexander
46c36d7f3e Implement babel call. 2023-12-30 18:15:58 -05:00
Tom Alexander
c46a935cfc Implement clock.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-30 18:12:09 -05:00
Tom Alexander
f50415cb32 Implement drawer. 2023-12-30 18:05:46 -05:00
Tom Alexander
4f1a151e97 Implement diary sexp. 2023-12-30 18:03:57 -05:00
Tom Alexander
c8e3fdba51 Implement horizontal rule. 2023-12-30 18:02:38 -05:00
Tom Alexander
4b3fc20c62 Fix order of reading optional pair values from elisp. 2023-12-30 18:00:26 -05:00
Tom Alexander
3131f8ac64 Implement example block and export block. 2023-12-30 17:55:56 -05:00
Tom Alexander
60a4835590 Implement comment block. 2023-12-30 17:44:32 -05:00
Tom Alexander
172d72aa46 Implement src block.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-30 17:40:15 -05:00
Tom Alexander
b4fcc6500b Implement verse block. 2023-12-30 16:47:24 -05:00
Tom Alexander
ddb6f31562 Implement angle link. 2023-12-30 16:41:55 -05:00
Tom Alexander
dc080b30fc Implement citation reference. 2023-12-30 16:35:01 -05:00
Tom Alexander
9901e17437 Implement citation. 2023-12-30 16:33:02 -05:00
Tom Alexander
ea000894f0 Implement entity. 2023-12-30 16:24:51 -05:00
Tom Alexander
e7742b529a Implement export snippet.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-30 16:18:08 -05:00
Tom Alexander
8eba0c4923 Implement footnote definition. 2023-12-30 16:13:54 -05:00
Tom Alexander
e0c0070a13 Implement footnote reference. 2023-12-30 16:05:41 -05:00
Tom Alexander
65ce116998 Implement inline babel call. 2023-12-30 15:52:48 -05:00
Tom Alexander
e348e7d4e3 Implement inline source block. 2023-12-30 13:13:35 -05:00
Tom Alexander
492090470c Implement latex environment. 2023-12-30 13:07:16 -05:00
Tom Alexander
3ec900c8df Implement latex fragment.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has failed
2023-12-30 13:00:07 -05:00
Tom Alexander
d0a008ed22 Implement org macro. 2023-12-30 13:00:07 -05:00
Tom Alexander
f2292f1c07 Implement target. 2023-12-30 12:22:23 -05:00
Tom Alexander
44392cfcca Implement radio target. 2023-12-30 12:17:04 -05:00
Tom Alexander
110630d230 Implement radio link and regular link. 2023-12-30 12:14:03 -05:00
Tom Alexander
ebe12d96c1 Implement subscript and superscript.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-29 23:46:47 -05:00
Tom Alexander
24c8ac8e21 Implement all the text markup.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-29 23:41:15 -05:00
Tom Alexander
259ad6e242 Implement line break. 2023-12-29 23:27:37 -05:00
Tom Alexander
dd1f7c7777 Support a no-op for headline pre-blank.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has failed
2023-12-29 23:21:30 -05:00
Tom Alexander
c1b471208d Implement plain list item.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-29 23:06:45 -05:00
Tom Alexander
606bab9e6d Fix handling of optval. 2023-12-29 22:58:32 -05:00
Tom Alexander
0edf5620a2 Implement plain list. 2023-12-29 22:04:34 -05:00
Tom Alexander
cdf87641c5 Implement comment. 2023-12-29 21:59:45 -05:00
Tom Alexander
eb2995dd3b Support list with empty string as only element for empty list. 2023-12-29 21:56:31 -05:00
Tom Alexander
cd6a64c015 Implement keyword. 2023-12-29 21:36:52 -05:00
Tom Alexander
a4a83d047d Fix node name getting chopped off.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
2023-12-29 21:33:17 -05:00
Tom Alexander
a4414369ce Remove unnecessary additional properties in the already-implemented types. 2023-12-29 21:04:31 -05:00
Tom Alexander
83e4b72307 Implement timestamp. 2023-12-29 20:55:01 -05:00
Tom Alexander
34b3e4fa7b Implement statistics cookie. 2023-12-29 20:26:12 -05:00
Tom Alexander
c0e879dc1e Implement headline. 2023-12-29 20:26:11 -05:00
Tom Alexander
fa31b001f4 Implement fixed width area. 2023-12-29 19:21:35 -05:00
Tom Alexander
0897061ff6 Add wasm tests to the CI. 2023-12-29 19:07:07 -05:00
Tom Alexander
28a3e1bc7b Implement bold. 2023-12-29 18:56:29 -05:00
Tom Alexander
3fd3d20722 Merge branch 'test_wasm_json' into wasm
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-29 18:54:58 -05:00
Tom Alexander
90735586b5 Add special case for object trees. 2023-12-29 18:54:41 -05:00
Tom Alexander
78befc7665 Remove old code. 2023-12-29 17:31:14 -05:00
Tom Alexander
ef549d3b19 Compare quoted strings. 2023-12-29 17:29:13 -05:00
Tom Alexander
777c756a7f Compare plain text AST nodes. 2023-12-29 17:24:38 -05:00
Tom Alexander
037caf369c Standardize parameter order. 2023-12-29 16:56:02 -05:00
Tom Alexander
54085b5833 Implement compare optional pair. 2023-12-29 16:51:52 -05:00
Tom Alexander
2bfa8e59e7 Add code to compare children. 2023-12-29 16:06:07 -05:00
Tom Alexander
5d31db39a4 Remove some underscores from wasm schema to match elisp. 2023-12-29 15:41:41 -05:00
Tom Alexander
adcd0de7e4 Compare standard properties. 2023-12-29 15:38:18 -05:00
Tom Alexander
c2f9789a64 Placeholder for comparing quoted strings. 2023-12-29 15:09:54 -05:00
Tom Alexander
579cbb5d11 Switch everything over to the new to_wasm macro. 2023-12-29 15:03:36 -05:00
Tom Alexander
cad2be43bf Implement a new to_wasm macro that uses the WasmAstNodeWrapper. 2023-12-29 14:06:10 -05:00
Tom Alexander
a0a4f0eb90 Remove lifetimes from wasm ast nodes. 2023-12-29 12:49:43 -05:00
Tom Alexander
9f4f8e79ce Implement a wrapper type for AST nodes.
This is to make it impossible to have a collision for attribute names that are real attributes vs attributes I've added for structure (like children and ast_node).
2023-12-29 11:58:46 -05:00
Tom Alexander
77e0dbb42e Start working on a version of compare based on json values.
This will be a better test because it will be testing that what we export to json is equivalent to the elisp AST generated from emacs. Because of these tests, we could also confidently use the wasm structure to elisp.
2023-12-29 11:37:30 -05:00
Tom Alexander
eff5cdbf40 Flatten some structures.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-29 10:04:59 -05:00
Tom Alexander
eef3571299 Add compare logic for optional pair.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-27 21:23:06 -05:00
Tom Alexander
f227d8405e Implement compare for list of quoted strings.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-27 21:00:12 -05:00
Tom Alexander
9520e5814b Add conversion for affiliated keywords to wasm additional properties. 2023-12-27 20:36:35 -05:00
Tom Alexander
28ad4fd046 Add conversion to WasmAstNode for wasm Objects. 2023-12-27 19:53:07 -05:00
Tom Alexander
7626a69fa1 Add default implementations for WasmElispCompare. 2023-12-27 19:42:45 -05:00
Tom Alexander
121c0ce516 Move the logic functions into their own module. 2023-12-27 19:22:43 -05:00
Tom Alexander
5a64db98fe Move wasm diff structs to their own module. 2023-12-27 19:15:39 -05:00
Tom Alexander
abfae9c6c0 Compare section.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-12-27 19:10:43 -05:00
Tom Alexander
5272e2f1b4 Start adding paragraph. 2023-12-27 18:47:59 -05:00
Tom Alexander
90d4b11922 Switch to a formatted print of the wasm compare status.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-27 18:39:10 -05:00
Tom Alexander
d552ef6569 Compare the additional properties. 2023-12-27 18:20:23 -05:00
Tom Alexander
f050e9b6a8 Taking into account additional property names but not comparing their values. 2023-12-27 18:01:56 -05:00
Tom Alexander
a5e108bc37 Compare the standard properties.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-27 17:07:42 -05:00
Tom Alexander
58290515b5 Enable child checking. 2023-12-27 16:47:02 -05:00
Tom Alexander
423f65046e Record the property comparisons. 2023-12-27 16:40:55 -05:00
Tom Alexander
badeaf8246 Add compare for document category. 2023-12-27 16:34:04 -05:00
Tom Alexander
d38100581c Add a script to run the wasm test inside docker. 2023-12-27 16:32:06 -05:00
Tom Alexander
f4eff5ca56 Fix wasm build.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-27 16:00:16 -05:00
Tom Alexander
5b02c21ebf Progress on comparing properties in the wasm_compare macro. 2023-12-27 15:58:31 -05:00
Tom Alexander
5f1668702a Starting the wasm_compare macro.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
2023-12-27 15:38:30 -05:00
Tom Alexander
1faaeeebf1 Simplify wasm diff result types.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-27 14:19:25 -05:00
Tom Alexander
20a7c89084 Improving WasmElispCompare. 2023-12-27 13:21:20 -05:00
Tom Alexander
e83417b243 Introducing a trait for running compares.
This should enable us to invoke compares without needing a reference ast node type.
2023-12-27 12:38:21 -05:00
Tom Alexander
36b80dc093 Separate out rust parsing step to support references to values stored in the parsed state.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-27 12:24:21 -05:00
Tom Alexander
1812b1a56e Remove phantom data. 2023-12-27 12:24:21 -05:00
Tom Alexander
1a70b3d2c0 Add a lifetime for data in the parsed result but not from the source. 2023-12-27 12:24:21 -05:00
Tom Alexander
abf066701e Add category and path to WasmDocument. 2023-12-27 11:31:35 -05:00
Tom Alexander
4984ea4179 More of the test structure. 2023-12-27 11:10:40 -05:00
Tom Alexander
3cb251ea6c Move terminal colors to the shared util module. 2023-12-27 10:57:40 -05:00
Tom Alexander
4bfea41291 Add more structure to the wasm compare. 2023-12-27 10:52:59 -05:00
Tom Alexander
99376515ef Invoking wasm_compare_document.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-27 09:31:54 -05:00
Tom Alexander
23f4ba4205 Serialize to wasm during wasm compare.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
2023-12-27 08:57:56 -05:00
Tom Alexander
55ad136283 Fix imports for wasm. 2023-12-27 08:49:34 -05:00
Tom Alexander
c717541099 Move the parsing of the elisp to the util module. 2023-12-27 08:46:18 -05:00
Tom Alexander
c2e921c2dc Move wasm test to a top-level module.
For some unknown reason, this makes rust-analyzer not angry.
2023-12-27 08:42:13 -05:00
Tom Alexander
e499169f0e Fix imports for wasm_test.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-27 08:37:34 -05:00
Tom Alexander
84c088df67 Add wasm targets to the build test in the CI.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-27 08:04:03 -05:00
Tom Alexander
f210f95f99 Use a temporary folder for the builds. 2023-12-26 21:23:20 -05:00
Tom Alexander
17b81c7c72 Add a script to build every possible feature combination. 2023-12-26 21:05:40 -05:00
Tom Alexander
2911fce7cc Put util under the library. 2023-12-26 20:18:41 -05:00
Tom Alexander
e622d9fa6b Remove the old implementation of print_versions.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-26 19:15:02 -05:00
Tom Alexander
8186fbb8b3 Move print_versions into a util crate. 2023-12-26 19:06:12 -05:00
Tom Alexander
68ccff74fa Outline for the wasm compare function. 2023-12-26 18:55:28 -05:00
Tom Alexander
9a13cb72c6 Make the wasm test binary async. 2023-12-25 14:32:01 -05:00
Tom Alexander
65abaa332f Separate out the wasm test into its own feature/binary. 2023-12-25 13:12:32 -05:00
Tom Alexander
67e5829fd9 Populating document's children. 2023-12-25 12:55:48 -05:00
Tom Alexander
995b41e697 Remove deserialize to support borrows. 2023-12-25 12:42:38 -05:00
Tom Alexander
eb51bdfe2f Add original field name to wasm macro. 2023-12-25 12:32:35 -05:00
Tom Alexander
bbb9ec637a Add code to test the wasm code path without actually dropping into wasm. 2023-12-25 12:14:50 -05:00
Tom Alexander
dc012b49f5 Add a generic WasmAstNode enum. 2023-12-25 11:51:39 -05:00
Tom Alexander
13863a68f7 Add placeholders for all the wasm ast nodes. 2023-12-25 11:33:43 -05:00
Tom Alexander
2962f76c81 Add lifetime to wasm objects. 2023-12-25 11:19:09 -05:00
Tom Alexander
b9b3ef6e74 Populate standard properties. 2023-12-25 10:47:10 -05:00
Tom Alexander
310ab2eab2 Add standard properties to wasm. 2023-12-24 15:26:45 -05:00
Tom Alexander
53320070da Define a wasm document. 2023-12-24 15:17:41 -05:00
Tom Alexander
2d5593681f Start defining the return type. 2023-12-24 13:02:34 -05:00
Tom Alexander
b3f97dbb40 Add wasm-bindgen. 2023-12-24 00:59:41 -05:00
Tom Alexander
a48d76321e Building basic wasm. 2023-12-24 00:47:32 -05:00
Tom Alexander
59222c58b1 Publish version 0.1.13.
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-15 21:10:21 -05:00
Tom Alexander
4d95a7f244 Merge branch 'post_blank'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-15 20:11:20 -05:00
Tom Alexander
5a8159eed7 Fix clippy.
All checks were successful
clippy Build clippy 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-12-15 19:57:35 -05:00
Tom Alexander
e24fcb9ded Add dummy values for new fields for plaintext. 2023-12-15 19:54:03 -05:00
Tom Alexander
4b94dc60d2 Fix handling of documents containing only whitespace. 2023-12-15 19:49:12 -05:00
Tom Alexander
2046603d01 Fix handling post blank for org documents. 2023-12-15 19:42:43 -05:00
Tom Alexander
30412361e1 Fix handling fixed width area post-blank inside a list. 2023-12-15 19:37:33 -05:00
Tom Alexander
e846c85188 Fix handling fixed width areas with empty lines in the middle. 2023-12-15 19:17:16 -05:00
Tom Alexander
99b74095e6 Fix heading post-blank. 2023-12-15 19:10:14 -05:00
Tom Alexander
6b802d36bf Implement the new fields for target. 2023-12-15 18:57:19 -05:00
Tom Alexander
33ca43ca40 Remove the old Paragraph::of_text function.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-15 18:06:48 -05:00
Tom Alexander
f5280a3090 Implement the new fields for bullshitium broken dynamic block. 2023-12-15 18:04:42 -05:00
Tom Alexander
c28d8ccea4 Fix post-blank for headlines containing only whitespace. 2023-12-15 17:59:47 -05:00
Tom Alexander
9690545901 Fix setting contents for broken end bullshitium when there is a paragraph present.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 17:40:08 -05:00
Tom Alexander
eba4fb94cf Implement the new fields for dynamic block. 2023-12-15 17:26:01 -05:00
Tom Alexander
565978225a Implement the new fields for table.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 17:16:49 -05:00
Tom Alexander
cce9ca87fa Fix handling of leading blank lines in greater blocks.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 16:55:47 -05:00
Tom Alexander
683c523ece Implement the new fields for greater block. 2023-12-15 16:15:22 -05:00
Tom Alexander
7a4dc20dc9 Implement the new fields for plain list.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 15:59:00 -05:00
Tom Alexander
022dda06eb Implement the new fields for plain list item. 2023-12-15 15:52:53 -05:00
Tom Alexander
7b88a2d248 Implement the new fields for broken end bullshitium. 2023-12-15 15:40:17 -05:00
Tom Alexander
fce5b92091 Remove leading blank lines from document contents. 2023-12-15 15:30:46 -05:00
Tom Alexander
45a506334c Remove leading blank lines from heading contents. 2023-12-15 15:20:31 -05:00
Tom Alexander
e47901a67f Implement the new fields for node property.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 15:05:03 -05:00
Tom Alexander
7430daa768 Fix handling of property drawers containing only whitespace. 2023-12-15 15:05:03 -05:00
Tom Alexander
6ce25c8a3b Update property drawer empty test to include a variety of whitespace. 2023-12-15 14:25:01 -05:00
Tom Alexander
7b8fa1eb4a Fix get_contents for sections. 2023-12-15 13:21:58 -05:00
Tom Alexander
ffa5349f25 Fix get_contents for headlines. 2023-12-15 13:14:49 -05:00
Tom Alexander
bb472b63cc Implement the new fields for property drawer. 2023-12-15 13:03:42 -05:00
Tom Alexander
57f566a7a1 Implement the new fields for planning. 2023-12-15 12:55:05 -05:00
Tom Alexander
2181993246 Implement the new fields for horizontal rule. 2023-12-15 12:50:01 -05:00
Tom Alexander
60d1ecfa75 Fix fixed width area to not consume trailing line break so it can be part of the post-blank.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 12:45:51 -05:00
Tom Alexander
3962db12a8 Implement the new fields for fixed width area. 2023-12-15 12:29:46 -05:00
Tom Alexander
f192507cd9 Implement the new fields for diary sexp.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 12:10:34 -05:00
Tom Alexander
252be3e001 Fix post blank for timestamp date ranges. 2023-12-15 11:38:52 -05:00
Tom Alexander
28f12a04f7 Implement the new fields for drawer.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 11:22:27 -05:00
Tom Alexander
d6232dc49c Implement the new fields for clock. 2023-12-15 10:49:04 -05:00
Tom Alexander
68a220aa1c Implement the new fields for babel call.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 10:45:16 -05:00
Tom Alexander
2e7db0f8bd Implement the new fields for lesser block.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 15:59:56 -05:00
Tom Alexander
175ff1e6c4 Implement the new fields for LaTeX environment.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 14:49:18 -05:00
Tom Alexander
0b42139393 Implement the new fields for inline babel call and inline source block. 2023-12-11 14:47:22 -05:00
Tom Alexander
67a9103b07 Implement the new fields for export snippet. 2023-12-11 14:41:49 -05:00
Tom Alexander
f141a4e186 Implement the new fields for citation.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 14:38:14 -05:00
Tom Alexander
aba29df34c Implement the new fields for org macro. 2023-12-11 14:22:56 -05:00
Tom Alexander
87ce7d7432 Implement the new fields for timestamp. 2023-12-11 14:18:04 -05:00
Tom Alexander
68dccd54b1 Implement the new fields for radio link. 2023-12-11 14:10:27 -05:00
Tom Alexander
4753f4c7c6 Implement the new fields for plain link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 13:08:53 -05:00
Tom Alexander
13c62bf29f Implement the new fields for angle link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 13:00:43 -05:00
Tom Alexander
670209e9fc Fix post blank for comment. 2023-12-11 12:58:05 -05:00
Tom Alexander
4af0d3141f Implement the new fields for statistics cookie. 2023-12-11 12:51:07 -05:00
Tom Alexander
ab281de3c6 Implement the new fields for latex fragment.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:46:20 -05:00
Tom Alexander
d556d28f49 Implement the new fields for entity. 2023-12-11 12:44:42 -05:00
Tom Alexander
9cfb2fa052 Implement the new fields for keywords.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:38:13 -05:00
Tom Alexander
30c03b5529 Implement the new fields for radio target. 2023-12-11 12:27:35 -05:00
Tom Alexander
b943f90766 Implement the new fields for regular link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:19:49 -05:00
Tom Alexander
0108f5b0b1 Implement the new fields for subscript and superscript.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:04:59 -05:00
Tom Alexander
50145c6cf2 Implement the new fields for line break.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-08 16:20:58 -05:00
Tom Alexander
4a8607726c Implement the new fields for comment. 2023-12-08 16:20:58 -05:00
Tom Alexander
9bcba4020d Implement the new fields for verbatim and code. 2023-12-08 16:20:58 -05:00
Tom Alexander
8fd9ff3848 Implement the new fields for bold, italic, underline, and strike-through. 2023-12-08 15:51:38 -05:00
Tom Alexander
3fb7cb82cd Implement get_contents for document.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-11-01 01:22:43 -04:00
Tom Alexander
e0ec5c115f Need a constant value for generic numbers. 2023-11-01 00:49:22 -04:00
Tom Alexander
f0868ba3ed Add a post blank implementation to document. 2023-10-31 23:56:40 -04:00
Tom Alexander
425bc12353 Add implementations to calculate the new fields for heading. 2023-10-31 23:46:53 -04:00
Tom Alexander
03754be71e Implement the new fields for section. 2023-10-31 23:16:57 -04:00
Tom Alexander
70002800c2 Implement the new fields for footnote definitions. 2023-10-31 23:12:04 -04:00
Tom Alexander
281c35677b Implement the new fields for paragraph. 2023-10-31 23:06:43 -04:00
Tom Alexander
92d15c3d91 Fix clippy. 2023-10-31 22:58:17 -04:00
Tom Alexander
b1773ac90e Get post blank for footnote references. 2023-10-31 22:58:17 -04:00
Tom Alexander
645d9abf9c Support nil contents. 2023-10-31 22:58:17 -04:00
Tom Alexander
d2f2bdf88d Implement get_contents for footnote references. 2023-10-31 22:58:17 -04:00
Tom Alexander
90ba17b68c Switch to a numeric post-blank.
Turns out post-blank has different meanings to different object types so we need to return a number to properly do the compare.
2023-10-31 22:32:01 -04:00
Tom Alexander
31406fd520 Fix clippy.
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-10-31 22:19:39 -04:00
Tom Alexander
49bc51ba89 Compare post-blank. 2023-10-31 22:18:28 -04:00
Tom Alexander
92592104a4 Compare contents begin/end. 2023-10-31 22:11:38 -04:00
Tom Alexander
33f4614d28 Make get_rust_byte_offsets more generic so it can be used for contents.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-31 21:59:58 -04:00
Tom Alexander
6c197c376a Add todo implementations of the new standard property functions. 2023-10-31 21:49:33 -04:00
Tom Alexander
bcf1b49db2 Remove the GetStandardProperties trait.
This was using dynamic dispatch to deal with enums to avoid the repetitive typing.
2023-10-31 21:26:00 -04:00
Tom Alexander
49f6e70a19 Use RPIT to get static dispatch GetStandardProperties. 2023-10-31 21:20:46 -04:00
Tom Alexander
31fb815681 Add a function for getting the post blank. 2023-10-31 21:20:46 -04:00
Tom Alexander
7dfe24ff98 Merge branch 'lazy_parse_lesser_block_contents'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 20:54:01 -04:00
Tom Alexander
a5627d0cee Do not parse the lesser block contents during parsing, but rather only if the contents are requested.
Some checks failed
rust-test Build rust-test has failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
This seemed like an unnecessary allocation during parsing, especially considering we throw away some parses based on whether or not we found radio targets in the source.
2023-10-31 20:43:08 -04:00
Tom Alexander
93cfa71df2 Merge branch 'foreign_document_literate_build_emacs'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 19:49:15 -04:00
Tom Alexander
78320d3265 Fix clippy errors.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 18:32:01 -04:00
Tom Alexander
9e908935f8 Add special case to delete invalid org-mode file.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-31 17:26:13 -04:00
Tom Alexander
b18a703529 Handle nil values for compare_property_object_tree. 2023-10-31 17:20:35 -04:00
Tom Alexander
ea52dc60be Add a literate tutorial for building emacs to the foreign documents test. 2023-10-31 16:33:11 -04:00
Tom Alexander
f5699ce830 Remove PartialEq from Object.
Some checks failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-10-31 16:33:10 -04:00
Tom Alexander
10aa0956ee Merge branch 'lesser_block_memory_optimization'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-27 22:13:25 -04:00
Tom Alexander
816c164996 Only allocate memory if removing text for lesser blocks.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-27 21:50:08 -04:00
Tom Alexander
ee201e1336 Merge branch 'explicit_all_node_iter'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-23 18:39:16 -04:00
Tom Alexander
4897952330 Make creating AllAstNodeIter explicit.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
This is to remove the ambiguity between calling iter on the specific structs like Document and calling iter on an AstNode by having an explicitly-named function to create the iterator.
2023-10-23 18:25:59 -04:00
Tom Alexander
e1d85c6dc2 Merge branch 'remove_set_source'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-23 18:06:56 -04:00
Tom Alexander
c420ccd029 Fix clippy errors.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-23 17:43:43 -04:00
Tom Alexander
a880629831 Make clippy not write to the host git repo. 2023-10-23 17:43:32 -04:00
Tom Alexander
5e2dea1f28 Remove the SetSource trait.
It was only being used for creating paragraphs of specific text, so I just adjusted the of_text function to handle it.
2023-10-23 17:43:32 -04:00
Tom Alexander
f47d688be4 Remove owned String from CustomError.
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
This is a 15% performance improvement.
2023-10-21 14:29:37 -04:00
Tom Alexander
acfc5e5e68 Only allocate memory when unquoting sexp string that contains escapes.
All checks were successful
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
If the quoted string contains no escape sequences, then unquoting the string can be done by simply shaving off the leading and trailing quotation marks which can be a slice operation. By returning Cow, we can return either a borrowed slice or an owned String.
2023-10-20 12:53:27 -04:00
Tom Alexander
503db94b2c Publish version 0.1.12.
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 19:01:43 -04:00
Tom Alexander
a4381e5e39 Merge branch 'keyword_constants' 2023-10-18 18:48:52 -04:00
Tom Alexander
e11de60def Clippy fixes.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 18:39:04 -04:00
Tom Alexander
b2479e9de8 Remove Debug from the context variables.
Now that entities are stored in the settings struct, these variables are massive which makes them balloon trace sizes while being mostly unreadable. This removes Debug from them to serve as a static-analysis check that context is ALWAYS ignored in tracing calls.
2023-10-18 18:36:25 -04:00
Tom Alexander
49d1cef7ae Remove context from functions that no longer need it. 2023-10-18 18:28:24 -04:00
Tom Alexander
ba72cc1b29 The variables for keywords are actually constants.
These settings do not need to exist in GlobalSettings because they are actually constants in upstream Org-Mode.
2023-10-18 18:22:01 -04:00
Tom Alexander
c58b0e7c35 Add a script to dump an AST using docker.
All checks were successful
rust-test Build rust-test has succeeded
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
2023-10-18 15:39:52 -04:00
Tom Alexander
f19d262825 Merge branch 'bullshitium'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy 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-10-18 13:04:16 -04:00
Tom Alexander
68f3f2e159 Clippy fixes.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 12:42:09 -04:00
Tom Alexander
269e23c1b1 No more expect-fail tests! 2023-10-18 12:41:12 -04:00
Tom Alexander
e111b8b9b8 Performance optimization. 2023-10-18 12:39:08 -04:00
Tom Alexander
353ff07420 Handle bullshitium for broken dynamic blocks. 2023-10-18 12:32:48 -04:00
Tom Alexander
94dec31130 Consuming trailing whitespace for 🔚 bullshitium. 2023-10-18 12:17:57 -04:00
Tom Alexander
cf5d3ed745 Add tests for the 🔚 bullshitium. 2023-10-18 11:59:55 -04:00
Tom Alexander
b0b287cd47 Handle bullshitium for 🔚. 2023-10-18 11:57:39 -04:00
Tom Alexander
bcdf1f5e9d Merge branch 'entity_special_case'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-18 08:53:57 -04:00
Tom Alexander
17d8e76e05 Do not match POST for entities that end with a space.
This is a special case for en-spaces.
2023-10-18 08:52:18 -04:00
Tom Alexander
8db9038c53 Merge branch 'list_perf_improvement'
Some checks failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
2023-10-18 08:40:19 -04:00
Tom Alexander
a276ba70e0 Fix empty content items with final item whitespace cut-off before headlines.
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-17 15:56:02 -04:00
Tom Alexander
b7442c1e92 Do not match headlines as plain list items. 2023-10-17 15:35:43 -04:00
Tom Alexander
364ba79517 It actually worked on trailing whitespace ownership test case 2. 2023-10-17 15:22:31 -04:00
Tom Alexander
47408763e5 A first stab at a final item whitespace cut-off exit matcher. 2023-10-17 15:08:36 -04:00
Tom Alexander
bd187ebfe7 Remove re-parsing of the final list child. 2023-10-17 14:17:47 -04:00
Tom Alexander
59cb3c2bbf Remove unnecessary closures in plain lists. 2023-10-17 13:59:33 -04:00
Tom Alexander
44f7412a5c Merge branch 'perf_improvement'
Some checks failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-17 13:54:44 -04:00
Tom Alexander
01464057ad Remove unused event types. 2023-10-17 13:43:33 -04:00
Tom Alexander
0208020e3e Also print byte offset.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-17 13:35:40 -04:00
Tom Alexander
a2f53361eb Record element start events and report them when the event_count feature is enabled. 2023-10-17 13:32:01 -04:00
Tom Alexander
17db05c2c7 Unify more error handling. 2023-10-17 12:42:34 -04:00
Tom Alexander
6139ea328d Unify some more error handling. 2023-10-17 12:22:52 -04:00
Tom Alexander
d20b4a410b Remove pointless map_err calls. 2023-10-17 11:56:36 -04:00
Tom Alexander
05c64f53b1 Remove boxed error from CustomError. 2023-10-17 11:40:11 -04:00
Tom Alexander
f65d0bb82d Remove redundant call to space0. 2023-10-17 11:33:26 -04:00
Tom Alexander
50d2831081 Cleanup. 2023-10-17 11:30:23 -04:00
Tom Alexander
bc9bd4f97b Eliminate some closures. 2023-10-17 11:10:18 -04:00
Tom Alexander
369d3e8c50 Add a full-document parse benchmark. 2023-10-17 10:57:04 -04:00
Tom Alexander
7d73eb6bd4 Merge branch 'error_rework'
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-10-17 10:40:46 -04:00
Tom Alexander
f59f153ee7 Clean up. 2023-10-17 10:39:21 -04:00
Tom Alexander
20c4a0f8f7 Continue removing MyError. 2023-10-17 10:35:33 -04:00
Tom Alexander
e776a051ad Continue removing MyError. 2023-10-17 10:13:00 -04:00
Tom Alexander
77e6c22ad8 Continue removing MyError. 2023-10-17 10:09:37 -04:00
Tom Alexander
c9d7251e3b Begin removing the MyError type. 2023-10-17 09:45:18 -04:00
Tom Alexander
8417b5fc9d Add an owned string entry for CustomError. 2023-10-17 09:27:15 -04:00
188 changed files with 8452 additions and 1517 deletions

View File

@@ -192,6 +192,54 @@ spec:
] ]
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-wasm
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-all
params:
- name: args
value:
[
"--target",
"wasm32-unknown-unknown",
"--profile",
"wasm",
"--bin",
"wasm",
"--no-default-features",
"--features",
"wasm",
]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-wasm-test
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-wasm
params:
- name: args
value:
[
"--bin",
"wasm_test",
"--no-default-features",
"--features",
"wasm_test",
]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally: finally:
- name: report-success - name: report-success
when: when:

View File

@@ -115,7 +115,7 @@ spec:
[ [
--no-default-features, --no-default-features,
--features, --features,
compare, "compare,wasm_test",
--no-fail-fast, --no-fail-fast,
--lib, --lib,
--test, --test,

View File

@@ -127,7 +127,7 @@ spec:
- name: command - name: command
value: ["cargo", "fix"] value: ["cargo", "fix"]
- name: args - name: args
value: ["--allow-dirty"] value: ["--all-targets", "--all-features", "--allow-dirty"]
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: commit-changes - name: commit-changes

View File

@@ -2,7 +2,7 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.11" version = "0.1.14"
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"
@@ -39,17 +39,32 @@ path = "src/lib.rs"
path = "src/bin_foreign_document_test.rs" path = "src/bin_foreign_document_test.rs"
required-features = ["foreign_document_test"] required-features = ["foreign_document_test"]
[[bin]]
name = "wasm"
path = "src/bin_wasm.rs"
required-features = ["wasm"]
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the library.
name = "wasm_test"
path = "src/bin_wasm_test.rs"
required-features = ["wasm_test"]
[dependencies] [dependencies]
futures = { version = "0.3.28", optional = true } futures = { version = "0.3.28", optional = true }
nom = "7.1.1" nom = "7.1.1"
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] } opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
opentelemetry-otlp = { version = "0.13.0", optional = true } opentelemetry-otlp = { version = "0.13.0", optional = true }
opentelemetry-semantic-conventions = { version = "0.12.0", optional = true } opentelemetry-semantic-conventions = { version = "0.12.0", optional = true }
serde = { version = "1.0.193", optional = true, features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.3", optional = true }
serde_json = { version = "1.0.108", optional = true }
tokio = { version = "1.30.0", optional = true, default-features = false, features = ["rt", "rt-multi-thread"] } tokio = { version = "1.30.0", optional = true, default-features = false, features = ["rt", "rt-multi-thread"] }
tracing = { version = "0.1.37", optional = true } tracing = { version = "0.1.37", optional = true }
tracing-opentelemetry = { version = "0.20.0", optional = true } tracing-opentelemetry = { version = "0.20.0", optional = true }
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
walkdir = { version = "2.3.3", optional = true } walkdir = { version = "2.3.3", optional = true }
wasm-bindgen = { version = "0.2.89", optional = true }
[build-dependencies] [build-dependencies]
walkdir = "2.3.3" walkdir = "2.3.3"
@@ -59,6 +74,9 @@ default = []
compare = ["tokio/process", "tokio/macros"] compare = ["tokio/process", "tokio/macros"]
foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"] foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"]
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"] tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
event_count = []
wasm = ["dep:serde", "dep:wasm-bindgen", "dep:serde-wasm-bindgen"]
wasm_test = ["wasm", "dep:serde_json", "tokio/process", "tokio/macros"]
# Optimized build for any sort of release. # Optimized build for any sort of release.
[profile.release-lto] [profile.release-lto]
@@ -78,3 +96,8 @@ strip = "symbols"
inherits = "release" inherits = "release"
lto = true lto = true
debug = true debug = true
[profile.wasm]
inherits = "release"
lto = true
strip = true

View File

@@ -29,6 +29,11 @@ build:
release: release:
> cargo build --release $(RELEASEFLAGS) > cargo build --release $(RELEASEFLAGS)
.PHONY: wasm
wasm:
> cargo build --target=wasm32-unknown-unknown --profile wasm --bin wasm --features wasm
> wasm-bindgen --target web --out-dir target/wasm32-unknown-unknown/js target/wasm32-unknown-unknown/wasm/wasm.wasm
.PHONY: clean .PHONY: clean
clean: clean:
> cargo clean > cargo clean
@@ -45,19 +50,24 @@ dockerclippy:
clippy: clippy:
> cargo clippy --no-deps --all-targets --all-features -- -D warnings > cargo clippy --no-deps --all-targets --all-features -- -D warnings
.PHONY: clippyfix
clippyfix:
> cargo clippy --fix --lib -p organic --all-features
.PHONY: test .PHONY: test
test: test:
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: doc
doc:
> cargo doc --no-deps --open --lib --release --all-features
.PHONY: dockertest .PHONY: dockertest
dockertest: dockertest:
> $(MAKE) -C docker/organic_test > $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > 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: dockerwasmtest
dockerwasmtest:
> $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare,wasm_test --no-fail-fast --lib --test test_loader autogen_wasm_ -- --test-threads $(TESTJOBS)
.PHONY: buildtest .PHONY: buildtest
buildtest: buildtest:
> cargo build --no-default-features > cargo build --no-default-features
@@ -66,6 +76,8 @@ buildtest:
> cargo build --no-default-features --features compare,tracing > cargo build --no-default-features --features compare,tracing
> cargo build --no-default-features --features compare,foreign_document_test > cargo build --no-default-features --features compare,foreign_document_test
> cargo build --no-default-features --features compare,tracing,foreign_document_test > cargo build --no-default-features --features compare,tracing,foreign_document_test
> cargo build --target wasm32-unknown-unknown --profile wasm --bin wasm --no-default-features --features wasm
> cargo build --bin wasm_test --no-default-features --features wasm_test
.PHONY: foreign_document_test .PHONY: foreign_document_test
foreign_document_test: foreign_document_test:

View File

@@ -66,10 +66,6 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
} }
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
fn is_expect_fail(name: &str) -> Option<&str> { fn is_expect_fail(_name: &str) -> Option<&str> {
match name { None
"greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
_ => None,
}
} }

View File

@@ -2,5 +2,6 @@ FROM rustlang/rust:nightly-alpine3.17
RUN apk add --no-cache musl-dev RUN apk add --no-cache musl-dev
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
RUN rustup target add wasm32-unknown-unknown
ENTRYPOINT ["cargo", "build"] ENTRYPOINT ["cargo", "build"]

View File

@@ -25,13 +25,13 @@ ifdef REMOTE_REPO
else else
@echo "REMOTE_REPO not defined, not removing from remote repo." @echo "REMOTE_REPO not defined, not removing from remote repo."
endif endif
docker volume rm cargo-cache docker volume rm rust-cache cargo-cache
# 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 --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME) docker run --rm --init -t --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)
.PHONY: shell .PHONY: shell
shell: build shell: build
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) 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)

View File

@@ -93,6 +93,12 @@ ARG WORG_PATH=/foreign_documents/worg
ARG WORG_REPO=https://git.sr.ht/~bzg/worg ARG WORG_REPO=https://git.sr.ht/~bzg/worg
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
ARG LITERATE_BUILD_EMACS_VERSION=e3ac1afe1e40af601be7af12c1d13d96308ab209
ARG LITERATE_BUILD_EMACS_PATH=/foreign_documents/literate_build_emacs
ARG LITERATE_BUILD_EMACS_REPO=https://gitlab.com/spudlyo/orgdemo2.git
RUN mkdir -p $LITERATE_BUILD_EMACS_PATH && git -C $LITERATE_BUILD_EMACS_PATH init --initial-branch=main && git -C $LITERATE_BUILD_EMACS_PATH remote add origin $LITERATE_BUILD_EMACS_REPO && git -C $LITERATE_BUILD_EMACS_PATH fetch origin $LITERATE_BUILD_EMACS_VERSION && git -C $LITERATE_BUILD_EMACS_PATH checkout FETCH_HEAD
# unused/aws.org contains invalid paths for setupfile which causes both upstream org-mode and Organic to error out.
RUN rm $LITERATE_BUILD_EMACS_PATH/unused/aws.org
FROM tester as foreign-document-test FROM tester as foreign-document-test
RUN apk add --no-cache bash coreutils RUN apk add --no-cache bash coreutils
@@ -100,6 +106,7 @@ RUN mkdir /foreign_documents
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
COPY --from=foreign-document-gather /foreign_documents/literate_build_emacs /foreign_documents/literate_build_emacs
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs COPY --from=build-emacs /root/emacs /foreign_documents/emacs
ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"] ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]

View File

@@ -0,0 +1,5 @@
#+caption:
#+caption: *foo*
#+caption[bar]:
#+begin_src bash
#+end_src

View File

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

View File

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

View File

View File

@@ -0,0 +1,4 @@

View File

@@ -0,0 +1,5 @@
* foo

View File

@@ -1,3 +1,32 @@
* Empty
:PROPERTIES:
:END:
* Single new line
:PROPERTIES: :PROPERTIES:
:END:
* Single line with spaces
:PROPERTIES:
:END:
* Many lines, first line without spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces, later line with spaces
:PROPERTIES:
:END: :END:

View File

@@ -5,3 +5,5 @@
#+call: dolar cat(dog) #+call: dolar cat(dog)
#+call: (bat) #+call: (bat)
#+call:

View File

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

View File

@@ -0,0 +1,6 @@
1. foo
#+begin_src text
#+end_src
2. baz

View File

@@ -1,3 +1,3 @@
foo <<bar>> baz <<FOO>> bar
lorem << ipsum >> dolar [[FOO][baz]]

View File

@@ -0,0 +1,5 @@
* foo
** bar
* baz

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env bash
#
# Time running a single parse without invoking a compare with emacs.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="debug"}
############## 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 {
if [ "$#" -gt 0 ]; then
export CARGO_TARGET_DIR="$1"
else
local work_directory=$(mktemp -d -t 'organic.XXXXXX')
folders+=("$work_directory")
export CARGO_TARGET_DIR="$work_directory"
fi
local features=(compare foreign_document_test tracing event_count wasm wasm_test)
ENABLED_FEATURES= for_each_combination "${features[@]}"
}
function for_each_combination {
local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
PROFILE="debug"
else
additional_flags+=(--profile "$PROFILE")
fi
local flag=$1
shift
if [ "$#" -gt 0 ]; then
ENABLED_FEATURES="$ENABLED_FEATURES" for_each_combination "${@}"
elif [ -z "$ENABLED_FEATURES" ]; then
(cd "$DIR/../" && printf "\n\n\n========== no features ==========\n\n\n" && set -x && cargo build "${additional_flags[@]}" --no-default-features)
else
(cd "$DIR/../" && printf "\n\n\n========== %s ==========\n\n\n" "${ENABLED_FEATURES:1}" && set -x && cargo build "${additional_flags[@]}" --no-default-features --features "${ENABLED_FEATURES:1}")
fi
ENABLED_FEATURES="$ENABLED_FEATURES,$flag"
if [ "$#" -gt 0 ]; then
ENABLED_FEATURES="$ENABLED_FEATURES" for_each_combination "${@}"
else
(cd "$DIR/../" && printf "\n\n\n========== %s ==========\n\n\n" "${ENABLED_FEATURES:1}" && set -x && cargo build "${additional_flags[@]}" --no-default-features --features "${ENABLED_FEATURES:1}")
fi
}
main "${@}"

58
scripts/dump_ast.bash Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
#
# Dump the AST of an org-mode document from emacs
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make)
############## Setup #########################
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
if [ $# -eq 0 ]; then
dump_ast_stdin "${@}"
else
dump_ast_file "${@}"
fi
}
function dump_ast_stdin {
# Until we can find a good way to encode stdin as an elisp string in bash, I cannot operate on stdin.
die 1 "This script only works on files."
}
function dump_ast_file {
local target_file mounted_file elisp_script
target_file=$($REALPATH "$1")
mounted_file="/input${target_file}"
elisp_script=$(cat <<EOF
(progn
(erase-buffer)
(require 'org)
(defun org-table-align () t)
(find-file-read-only "${mounted_file}")
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)
EOF
)
exec docker run --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" --entrypoint "" organic-test emacs -q --no-site-file --no-splash --batch --eval "$elisp_script"
}
main "${@}"

View File

@@ -14,7 +14,7 @@ function main {
additional_flags+=(--profile "$PROFILE") additional_flags+=(--profile "$PROFILE")
fi fi
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}") (cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
perf record --freq=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}" perf record --freq=70000 --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

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${SHELL:="NO"} # or YES to launch a shell instead of running the test
: ${TRACE:="NO"} # or YES to send traces to jaeger
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
: ${NO_COLOR:=""} # Set to anything to disable color output
: ${PROFILE:="debug"}
REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make)
############## Setup #########################
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
build_container
launch_container "${@}"
}
function build_container {
$MAKE -C "$DIR/../docker/organic_test"
}
function launch_container {
local additional_flags=()
local features=(wasm_test)
if [ "$NO_COLOR" != "" ]; then
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
fi
if [ "$TRACE" = "YES" ]; then
# We use the host network so it can talk to jaeger hosted at 127.0.0.1
additional_flags+=(--network=host --env RUST_LOG=debug)
features+=(tracing)
fi
if [ "$SHELL" != "YES" ]; then
additional_flags+=(--read-only)
else
additional_flags+=(-t)
fi
if [ "$BACKTRACE" = "YES" ]; then
additional_flags+=(--env RUST_BACKTRACE=full)
fi
if [ "$SHELL" = "YES" ]; then
exec docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/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 /bin/sh
fi
local features_joined
features_joined=$(IFS=","; echo "${features[*]}")
local build_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
PROFILE="debug"
else
build_flags+=(--profile "$PROFILE")
fi
if [ $# -gt 0 ]; then
# If we passed in args, we need to forward them along
for path in "${@}"; do
local full_path
full_path=$($REALPATH "$path")
init_script=$(cat <<EOF
set -euo pipefail
IFS=\$'\n\t'
cargo build --bin wasm_test --no-default-features --features "$features_joined" ${build_flags[@]}
exec /target/${PROFILE}/wasm_test "/input${full_path}"
EOF
)
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/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 sh -c "$init_script"
done
else
local current_directory init_script
current_directory=$(pwd)
init_script=$(cat <<EOF
set -euo pipefail
IFS=\$'\n\t'
cargo build --bin wasm_test --no-default-features --features "$features_joined" ${build_flags[@]}
cd /input${current_directory}
exec /target/${PROFILE}/wasm_test
EOF
)
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/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 sh -c "$init_script"
fi
}
main "${@}"

View File

@@ -1,3 +1,4 @@
#![feature(exit_status_error)]
#![feature(round_char_boundary)] #![feature(round_char_boundary)]
#![feature(exact_size_is_empty)] #![feature(exact_size_is_empty)]
use std::io::Read; use std::io::Read;

View File

@@ -53,6 +53,9 @@ async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
let layer = layer.chain(compare_group("doomemacs", || { let layer = layer.chain(compare_group("doomemacs", || {
compare_all_org_document("/foreign_documents/doomemacs") compare_all_org_document("/foreign_documents/doomemacs")
})); }));
let layer = layer.chain(compare_group("literate_build_emacs", || {
compare_all_org_document("/foreign_documents/literate_build_emacs")
}));
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect(); let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
let mut any_failed = false; let mut any_failed = false;

10
src/bin_wasm.rs Normal file
View File

@@ -0,0 +1,10 @@
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn parse_org(org_contents: &str) -> wasm_bindgen::JsValue {
organic::wasm_cli::parse_org(org_contents)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

62
src/bin_wasm_test.rs Normal file
View File

@@ -0,0 +1,62 @@
#![feature(exact_size_is_empty)]
#![feature(exit_status_error)]
use std::io::Read;
use organic::wasm_test::wasm_run_anonymous_compare;
use organic::wasm_test::wasm_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>> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let main_body_result = main_body().await;
main_body_result
})
}
#[cfg(feature = "tracing")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
init_telemetry()?;
let main_body_result = main_body().await;
shutdown_telemetry()?;
main_body_result
})
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
async 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()?;
if wasm_run_anonymous_compare(org_contents).await? {
} else {
Err("Diff results do not match.")?;
}
Ok(())
} else {
for arg in args {
if wasm_run_compare_on_file(arg).await? {
} else {
Err("Diff results do not match.")?;
}
}
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)
}

View File

@@ -1,16 +1,16 @@
use std::path::Path; use std::path::Path;
use crate::compare::diff::compare_document; use crate::compare::diff::compare_document;
use crate::compare::diff::DiffResult;
use crate::compare::parse::emacs_parse_anonymous_org_document;
use crate::compare::parse::emacs_parse_file_org_document;
use crate::compare::parse::get_emacs_version;
use crate::compare::parse::get_org_mode_version;
use crate::compare::sexp::sexp;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::LocalFileAccessInterface; use crate::context::LocalFileAccessInterface;
use crate::parser::parse_file_with_settings; use crate::parser::parse_file_with_settings;
use crate::parser::parse_with_settings; use crate::parser::parse_with_settings;
use crate::util::cli::emacs_parse_anonymous_org_document;
use crate::util::cli::emacs_parse_file_org_document;
use crate::util::cli::print_versions;
use crate::util::elisp::sexp;
use crate::util::terminal::foreground_color;
use crate::util::terminal::reset_color;
pub async fn run_anonymous_compare<P: AsRef<str>>( pub async fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P, org_contents: P,
@@ -68,8 +68,8 @@ pub async fn run_anonymous_compare_with_settings<'g, 's, P: AsRef<str>>(
} else if !silent { } else if !silent {
println!( println!(
"{color}Entire document passes.{reset}", "{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0), color = foreground_color(0, 255, 0),
reset = DiffResult::reset_color(), reset = reset_color(),
); );
} }
@@ -121,19 +121,10 @@ pub async fn run_compare_on_file_with_settings<'g, 's, P: AsRef<Path>>(
} else if !silent { } else if !silent {
println!( println!(
"{color}Entire document passes.{reset}", "{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0), color = foreground_color(0, 255, 0),
reset = DiffResult::reset_color(), reset = reset_color(),
); );
} }
Ok(true) Ok(true)
} }
async fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("Using emacs version: {}", get_emacs_version().await?.trim());
eprintln!(
"Using org-mode version: {}",
get_org_mode_version().await?.trim()
);
Ok(())
}

View File

@@ -1,3 +1,5 @@
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::Debug; use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
@@ -7,8 +9,6 @@ use super::diff::artificial_owned_diff_scope;
use super::diff::compare_ast_node; use super::diff::compare_ast_node;
use super::diff::DiffEntry; use super::diff::DiffEntry;
use super::diff::DiffStatus; use super::diff::DiffStatus;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::get_property; use super::util::get_property;
use super::util::get_property_numeric; use super::util::get_property_numeric;
use super::util::get_property_quoted_string; use super::util::get_property_quoted_string;
@@ -18,6 +18,8 @@ use crate::types::CharOffsetInLine;
use crate::types::LineNumber; use crate::types::LineNumber;
use crate::types::RetainLabels; use crate::types::RetainLabels;
use crate::types::SwitchNumberLines; use crate::types::SwitchNumberLines;
use crate::util::elisp::unquote;
use crate::util::elisp::Token;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum EmacsField<'s> { pub(crate) enum EmacsField<'s> {
@@ -262,11 +264,11 @@ pub(crate) fn compare_property_set_of_quoted_string<
.iter() .iter()
.map(|e| e.as_atom()) .map(|e| e.as_atom())
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let value: Vec<String> = value let value: Vec<Cow<'_, str>> = value
.into_iter() .into_iter()
.map(unquote) .map(unquote)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let value: BTreeSet<&str> = value.iter().map(|e| e.as_str()).collect(); let value: BTreeSet<&str> = value.iter().map(|e| e.borrow()).collect();
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect(); let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
if !mismatched.is_empty() { if !mismatched.is_empty() {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
@@ -546,6 +548,21 @@ where
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len()); let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) { for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) {
match (kw_e.as_atom(), kw_r) {
(Ok("nil"), (None, mandatory_value)) if mandatory_value.is_empty() => {
// If its an empty keyword then it becomes nil in the elisp.
continue;
}
(Ok("nil"), _) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
_ => {}
}
let kw_e = kw_e.as_list()?; let kw_e = kw_e.as_list()?;
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0); let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length); let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
@@ -554,6 +571,17 @@ where
let mut kw_e = kw_e.iter(); let mut kw_e = kw_e.iter();
// First element is a list representing the mandatory value. // First element is a list representing the mandatory value.
if let Some(val_e) = kw_e.next() { if let Some(val_e) = kw_e.next() {
match (val_e.as_atom(), kw_r) {
(Ok("nil"), (_, mandatory_value)) if mandatory_value.is_empty() => {}
(Ok("nil"), _) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
_ => {
let el = val_e.as_list()?; let el = val_e.as_list()?;
if el.len() != kw_r.1.len() { if el.len() != kw_r.1.len() {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
@@ -566,6 +594,8 @@ where
for (e, r) in el.iter().zip(kw_r.1.iter()) { for (e, r) in el.iter().zip(kw_r.1.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?); child_status.push(compare_ast_node(source, e, r.into())?);
} }
}
};
} else { } else {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
@@ -653,7 +683,7 @@ pub(crate) fn compare_property_number_lines<
(Some(number_lines), Some(rust_number_lines)) => { (Some(number_lines), Some(rust_number_lines)) => {
let token_list = number_lines.as_list()?; let token_list = number_lines.as_list()?;
let number_type = token_list let number_type = token_list
.get(0) .first()
.map(Token::as_atom) .map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))? .map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a type.")?; .ok_or(":number-lines should have a type.")?;

View File

@@ -16,10 +16,6 @@ use super::compare_field::compare_property_retain_labels;
use super::compare_field::compare_property_set_of_quoted_string; use super::compare_field::compare_property_set_of_quoted_string;
use super::compare_field::compare_property_single_ast_node; use super::compare_field::compare_property_single_ast_node;
use super::compare_field::compare_property_unquoted_atom; use super::compare_field::compare_property_unquoted_atom;
use super::elisp_fact::ElispFact;
use super::elisp_fact::GetElispFact;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::affiliated_keywords_names; use super::util::affiliated_keywords_names;
use super::util::assert_no_children; use super::util::assert_no_children;
use super::util::compare_additional_properties; use super::util::compare_additional_properties;
@@ -57,7 +53,6 @@ use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition; use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference; use crate::types::FootnoteReference;
use crate::types::FootnoteReferenceType; use crate::types::FootnoteReferenceType;
use crate::types::GetStandardProperties;
use crate::types::Heading; use crate::types::Heading;
use crate::types::HorizontalRule; use crate::types::HorizontalRule;
use crate::types::Hour; use crate::types::Hour;
@@ -110,6 +105,12 @@ use crate::types::Verbatim;
use crate::types::VerseBlock; use crate::types::VerseBlock;
use crate::types::WarningDelayType; use crate::types::WarningDelayType;
use crate::types::Year; use crate::types::Year;
use crate::util::elisp::unquote;
use crate::util::elisp::Token;
use crate::util::elisp_fact::ElispFact;
use crate::util::elisp_fact::GetElispFact;
use crate::util::terminal::foreground_color;
use crate::util::terminal::reset_color;
#[derive(Debug)] #[derive(Debug)]
pub enum DiffEntry<'b, 's> { pub enum DiffEntry<'b, 's> {
@@ -128,7 +129,7 @@ pub struct DiffResult<'b, 's> {
emacs_token: &'b Token<'s>, emacs_token: &'b Token<'s>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub(crate) enum DiffStatus { pub(crate) enum DiffStatus {
Good, Good,
Bad, Bad,
@@ -164,7 +165,7 @@ impl<'b, 's> DiffEntry<'b, 's> {
fn is_immediately_bad(&self) -> bool { fn is_immediately_bad(&self) -> bool {
match self { match self {
DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad, DiffEntry::DiffResult(diff) => matches!(diff.status, DiffStatus::Bad),
DiffEntry::DiffLayer(_) => false, DiffEntry::DiffLayer(_) => false,
} }
} }
@@ -201,21 +202,21 @@ impl<'b, 's> DiffResult<'b, 's> {
if self.has_bad_children() { if self.has_bad_children() {
format!( format!(
"{color}BADCHILD{reset}", "{color}BADCHILD{reset}",
color = DiffResult::foreground_color(255, 255, 0), color = foreground_color(255, 255, 0),
reset = DiffResult::reset_color(), reset = reset_color(),
) )
} else { } else {
format!( format!(
"{color}GOOD{reset}", "{color}GOOD{reset}",
color = DiffResult::foreground_color(0, 255, 0), color = foreground_color(0, 255, 0),
reset = DiffResult::reset_color(), reset = reset_color(),
) )
} }
} }
DiffStatus::Bad => format!( DiffStatus::Bad => format!(
"{color}BAD{reset}", "{color}BAD{reset}",
color = DiffResult::foreground_color(255, 0, 0), color = foreground_color(255, 0, 0),
reset = DiffResult::reset_color(), reset = reset_color(),
), ),
} }
}; };
@@ -240,45 +241,6 @@ impl<'b, 's> DiffResult<'b, 's> {
.iter() .iter()
.any(|child| child.is_immediately_bad() || child.has_bad_children()) .any(|child| child.is_immediately_bad() || child.has_bad_children())
} }
pub(crate) fn foreground_color(red: u8, green: u8, blue: u8) -> String {
if DiffResult::should_use_color() {
format!(
"\x1b[38;2;{red};{green};{blue}m",
red = red,
green = green,
blue = blue
)
} else {
String::new()
}
}
#[allow(dead_code)]
pub(crate) fn background_color(red: u8, green: u8, blue: u8) -> String {
if DiffResult::should_use_color() {
format!(
"\x1b[48;2;{red};{green};{blue}m",
red = red,
green = green,
blue = blue
)
} else {
String::new()
}
}
pub(crate) fn reset_color() -> &'static str {
if DiffResult::should_use_color() {
"\x1b[0m"
} else {
""
}
}
fn should_use_color() -> bool {
!std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
}
} }
impl<'b, 's> DiffLayer<'b, 's> { impl<'b, 's> DiffLayer<'b, 's> {
@@ -296,14 +258,14 @@ impl<'b, 's> DiffLayer<'b, 's> {
let status_text = if self.has_bad_children() { let status_text = if self.has_bad_children() {
format!( format!(
"{color}BADCHILD{reset}", "{color}BADCHILD{reset}",
color = DiffResult::foreground_color(255, 255, 0), color = foreground_color(255, 255, 0),
reset = DiffResult::reset_color(), reset = reset_color(),
) )
} else { } else {
format!( format!(
"{color}GOOD{reset}", "{color}GOOD{reset}",
color = DiffResult::foreground_color(0, 255, 0), color = foreground_color(0, 255, 0),
reset = DiffResult::reset_color(), reset = reset_color(),
) )
}; };
println!( println!(
@@ -413,7 +375,7 @@ pub(crate) fn compare_ast_node<'b, 's>(
name: rust.get_elisp_fact().get_elisp_name(), name: rust.get_elisp_fact().get_elisp_name(),
message: Some(e.to_string()), message: Some(e.to_string()),
children: Vec::new(), children: Vec::new(),
rust_source: rust.get_standard_properties().get_source(), rust_source: rust.get_source(),
emacs_token: emacs, emacs_token: emacs,
} }
.into() .into()
@@ -1576,7 +1538,7 @@ fn compare_example_block<'b, 's>(
[], [],
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents.as_str()), |r| Some(r.get_value()),
compare_property_quoted_string compare_property_quoted_string
), ),
( (
@@ -1654,7 +1616,7 @@ fn compare_export_block<'b, 's>(
), ),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents.as_str()), |r| Some(r.get_value()),
compare_property_quoted_string compare_property_quoted_string
) )
) { ) {
@@ -1702,7 +1664,7 @@ fn compare_src_block<'b, 's>(
), ),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents.as_str()), |r| Some(r.get_value()),
compare_property_quoted_string compare_property_quoted_string
), ),
( (
@@ -2153,7 +2115,7 @@ fn compare_plain_text<'b, 's>(
let text = emacs.as_text()?; let text = emacs.as_text()?;
let start_ind: usize = text let start_ind: usize = text
.properties .properties
.get(0) .first()
.expect("Should have start index.") .expect("Should have start index.")
.as_atom()? .as_atom()?
.parse()?; .parse()?;

View File

@@ -2,10 +2,7 @@
mod compare; mod compare;
mod compare_field; mod compare_field;
mod diff; mod diff;
mod elisp_fact;
mod macros; mod macros;
mod parse;
mod sexp;
mod util; mod util;
pub use compare::run_anonymous_compare; pub use compare::run_anonymous_compare;
pub use compare::run_anonymous_compare_with_settings; pub use compare::run_anonymous_compare_with_settings;

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::str::FromStr; use std::str::FromStr;
use super::compare_field::compare_property_list_of_quoted_string; use super::compare_field::compare_property_list_of_quoted_string;
@@ -7,15 +8,15 @@ use super::compare_field::compare_property_quoted_string;
use super::compare_field::ComparePropertiesResult; use super::compare_field::ComparePropertiesResult;
use super::diff::DiffEntry; use super::diff::DiffEntry;
use super::diff::DiffStatus; use super::diff::DiffStatus;
use super::elisp_fact::GetElispFact;
use super::sexp::Token;
use crate::compare::diff::compare_ast_node; use crate::compare::diff::compare_ast_node;
use crate::compare::sexp::unquote;
use crate::types::AffiliatedKeywordValue; use crate::types::AffiliatedKeywordValue;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::GetAffiliatedKeywords; use crate::types::GetAffiliatedKeywords;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties; use crate::types::StandardProperties;
use crate::util::elisp::get_emacs_standard_properties;
use crate::util::elisp::unquote;
use crate::util::elisp::Token;
use crate::util::elisp_fact::GetElispFact;
/// 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 {
@@ -29,32 +30,29 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get the byte offset into source that the rust object exists at. /// Get the byte 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.
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>( fn get_rust_byte_offsets(original_document: &str, subset: &str) -> (usize, usize) {
original_document: &'s str, debug_assert!(is_slice_of(original_document, subset));
rust_ast_node: &'b S, let offset = subset.as_ptr() as usize - original_document.as_ptr() as usize;
) -> (usize, usize) { let end = offset + subset.len();
let rust_object_source = rust_ast_node.get_source();
debug_assert!(is_slice_of(original_document, rust_object_source));
let offset = rust_object_source.as_ptr() as usize - original_document.as_ptr() as usize;
let end = offset + rust_object_source.len();
(offset, end) (offset, end)
} }
pub(crate) fn compare_standard_properties< pub(crate) fn compare_standard_properties<
'b, 'b,
's, 's,
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized, S: StandardProperties<'s> + GetElispFact<'s> + ?Sized,
>( >(
original_document: &'s str, original_document: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust: &'b S, rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?; assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
assert_bounds(original_document, emacs, rust.get_standard_properties())?; assert_bounds(original_document, emacs, rust)?;
assert_post_blank(emacs, rust)?;
Ok(()) Ok(())
} }
pub(crate) fn assert_name<S: AsRef<str>>( fn assert_name<S: AsRef<str>>(
emacs: &Token<'_>, emacs: &Token<'_>,
name: S, name: S,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
@@ -77,101 +75,75 @@ pub(crate) fn assert_name<S: AsRef<str>>(
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties. /// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
/// ///
/// This does **not** handle plain text because plain text is a special case. /// This does **not** handle plain text because plain text is a special case.
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>( fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
original_document: &'s str, original_document: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust: &'b S, rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
// Check begin/end
{
let (begin, end) = ( let (begin, end) = (
standard_properties standard_properties
.begin .begin
.ok_or("Token should have a begin.")?, .ok_or("Token should have a begin.")?,
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_rust_byte_offsets(original_document, rust); // 0-based let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust.get_source()); // 0-based
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
let rust_end_char_offset = let rust_end_char_offset =
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
if rust_begin_char_offset != begin || rust_end_char_offset != end { if rust_begin_char_offset != begin || rust_end_char_offset != 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, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=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, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
} }
}
// Check contents-begin/contents-end
{
if let Some(rust_contents) = rust.get_contents() {
let (begin, end) = (
standard_properties
.contents_begin
.ok_or("Token should have a contents-begin.")?,
standard_properties
.contents_end
.ok_or("Token should have an contents-end.")?,
);
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust_contents); // 0-based
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
let rust_end_char_offset =
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
if rust_begin_char_offset != begin || rust_end_char_offset != end {
Err(format!("Rust contents bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs contents bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
}
} else if standard_properties.contents_begin.is_some()
|| standard_properties.contents_end.is_some()
{
Err(format!("Rust contents is None but emacs contents bounds are ({emacs_begin:?}, {emacs_end:?})", emacs_begin=standard_properties.contents_begin, emacs_end=standard_properties.contents_end))?;
}
}
Ok(()) Ok(())
} }
struct EmacsStandardProperties { /// Assert that the post blank matches between emacs and organic.
begin: Option<usize>, ///
#[allow(dead_code)] /// This does **not** handle plain text because plain text is a special case.
post_affiliated: Option<usize>, fn assert_post_blank<'b, 's, S: StandardProperties<'s> + ?Sized>(
#[allow(dead_code)] emacs: &'b Token<'s>,
contents_begin: Option<usize>, rust: &'b S,
#[allow(dead_code)] ) -> Result<(), Box<dyn std::error::Error>> {
contents_end: Option<usize>, let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
end: Option<usize>, let rust_post_blank = rust.get_post_blank();
#[allow(dead_code)] let emacs_post_blank = standard_properties
post_blank: Option<usize>, .post_blank
} .ok_or("Token should have a post-blank.")?;
if rust_post_blank as usize != emacs_post_blank {
Err(format!("Rust post-blank {rust_post_blank} does not match emacs post-blank ({emacs_post_blank})", rust_post_blank = rust_post_blank, emacs_post_blank = emacs_post_blank))?;
}
fn get_emacs_standard_properties( Ok(())
emacs: &Token<'_>,
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let standard_properties = attributes_map.get(":standard-properties");
Ok(if standard_properties.is_some() {
let mut std_props = standard_properties
.expect("if statement proves its Some")
.as_vector()?
.iter();
let begin = maybe_token_to_usize(std_props.next())?;
let post_affiliated = maybe_token_to_usize(std_props.next())?;
let contents_begin = maybe_token_to_usize(std_props.next())?;
let contents_end = maybe_token_to_usize(std_props.next())?;
let end = maybe_token_to_usize(std_props.next())?;
let post_blank = maybe_token_to_usize(std_props.next())?;
EmacsStandardProperties {
begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
} else {
let begin = maybe_token_to_usize(attributes_map.get(":begin").copied())?;
let end = maybe_token_to_usize(attributes_map.get(":end").copied())?;
let contents_begin = maybe_token_to_usize(attributes_map.get(":contents-begin").copied())?;
let contents_end = maybe_token_to_usize(attributes_map.get(":contents-end").copied())?;
let post_blank = maybe_token_to_usize(attributes_map.get(":post-blank").copied())?;
let post_affiliated =
maybe_token_to_usize(attributes_map.get(":post-affiliated").copied())?;
EmacsStandardProperties {
begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
})
}
fn maybe_token_to_usize(
token: Option<&Token<'_>>,
) -> Result<Option<usize>, Box<dyn std::error::Error>> {
Ok(token
.map(|token| token.as_atom())
.map_or(Ok(None), |r| r.map(Some))?
.and_then(|val| {
if val == "nil" {
None
} else {
Some(val.parse::<usize>())
}
})
.map_or(Ok(None), |r| r.map(Some))?)
} }
/// Get a named property from the emacs token. /// Get a named property from the emacs token.
@@ -206,10 +178,10 @@ pub(crate) fn get_property_unquoted_atom<'s>(
/// Get a named property containing an quoted string from the emacs token. /// Get a named property containing an quoted string from the emacs token.
/// ///
/// Returns None if key is not found. /// Returns None if key is not found.
pub(crate) fn get_property_quoted_string( pub(crate) fn get_property_quoted_string<'s>(
emacs: &Token<'_>, emacs: &Token<'s>,
key: &str, key: &str,
) -> Result<Option<String>, Box<dyn std::error::Error>> { ) -> Result<Option<Cow<'s, str>>, Box<dyn std::error::Error>> {
get_property(emacs, key)? get_property(emacs, key)?
.map(Token::as_atom) .map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))? .map_or(Ok(None), |r| r.map(Some))?
@@ -240,7 +212,7 @@ where
pub(crate) fn compare_children<'b, 's, 'x, RC>( pub(crate) fn compare_children<'b, 's, 'x, RC>(
source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust_children: &'x Vec<RC>, rust_children: &'x [RC],
child_status: &mut Vec<DiffEntry<'b, 's>>, child_status: &mut Vec<DiffEntry<'b, 's>>,
this_status: &mut DiffStatus, this_status: &mut DiffStatus,
message: &mut Option<String>, message: &mut Option<String>,

View File

@@ -1,15 +1,27 @@
use super::global_settings::EntityDefinition; use super::global_settings::EntityDefinition;
pub(crate) const DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS: [&str; 1] = ["CAPTION"]; /// Keywords that contain the standard set of objects (excluding footnote references).
///
/// Corresponds to org-element-parsed-keywords elisp variable.
pub(crate) const ORG_ELEMENT_PARSED_KEYWORDS: [&str; 1] = ["CAPTION"];
pub(crate) const DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS: [&str; 2] = ["CAPTION", "RESULTS"]; /// Keywords that can have a secondary value in square brackets.
///
/// Corresponds to org-element-dual-keywords elisp variable.
pub(crate) const ORG_ELEMENT_DUAL_KEYWORDS: [&str; 2] = ["CAPTION", "RESULTS"];
pub(crate) const DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS: [&str; 13] = [ /// Keywords that can be affiliated with an element.
///
/// Corresponds to org-element-affiliated-keywords elisp variable.
pub(crate) const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&str; 13] = [
"CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT", "CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT",
"RESULTS", "SOURCE", "SRCNAME", "TBLNAME", "RESULTS", "SOURCE", "SRCNAME", "TBLNAME",
]; ];
pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&str, &str); 8] = [ /// Mapping of keyword names.
///
/// Corresponds to org-element-keyword-translation-alist elisp variable.
pub(crate) const ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&str, &str); 8] = [
("DATA", "NAME"), ("DATA", "NAME"),
("LABEL", "NAME"), ("LABEL", "NAME"),
("RESNAME", "NAME"), ("RESNAME", "NAME"),

View File

@@ -9,11 +9,9 @@ use super::list::List;
use super::DynContextMatcher; use super::DynContextMatcher;
use super::RefContext; use super::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::OrgSource; use crate::parser::OrgSource;
#[derive(Debug)]
pub(crate) enum ContextElement<'r, 's> { pub(crate) enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher. /// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>), ExitMatcherNode(ExitMatcherNode<'r>),
@@ -35,15 +33,6 @@ pub(crate) struct ExitMatcherNode<'r> {
pub(crate) class: ExitClass, 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> { pub(crate) struct Context<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>, tree: List<'r, &'r ContextElement<'r, 's>>,
@@ -108,7 +97,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
pub(crate) fn check_exit_matcher( pub(crate) fn check_exit_matcher(
&'r self, &'r self,
i: OrgSource<'s>, i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> { ) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError> {
let mut current_class_filter = ExitClass::Gamma; let mut current_class_filter = ExitClass::Gamma;
for current_node in self.iter_context() { for current_node in self.iter_context() {
let context_element = current_node.get_data(); let context_element = current_node.get_data();
@@ -123,7 +112,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
} }
} }
// TODO: Make this a specific error instead of just a generic MyError // TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError("NoExit")))); return Err(nom::Err::Error(CustomError::Static("NoExit")));
} }
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.

View File

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

View File

@@ -1,17 +1,16 @@
use std::fmt::Debug;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(any(feature = "compare", feature = "foreign_document_test"))] #[cfg(any(feature = "compare", feature = "foreign_document_test"))]
pub trait FileAccessInterface: Sync + Debug { pub trait FileAccessInterface: Sync {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>; fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
} }
#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))] #[cfg(not(any(feature = "compare", feature = "foreign_document_test")))]
pub trait FileAccessInterface: Debug { pub trait FileAccessInterface {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>; fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
} }
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct LocalFileAccessInterface { pub struct LocalFileAccessInterface {
pub working_directory: Option<PathBuf>, pub working_directory: Option<PathBuf>,
} }

View File

@@ -5,16 +5,12 @@ use super::constants::DEFAULT_ORG_ENTITIES;
use super::constants::DEFAULT_ORG_LINK_PARAMETERS; use super::constants::DEFAULT_ORG_LINK_PARAMETERS;
use super::FileAccessInterface; use super::FileAccessInterface;
use super::LocalFileAccessInterface; use super::LocalFileAccessInterface;
use crate::context::constants::DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
use crate::context::constants::DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS;
use crate::types::IndentationLevel; use crate::types::IndentationLevel;
use crate::types::Object; use crate::types::Object;
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html // TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct GlobalSettings<'g, 's> { pub struct GlobalSettings<'g, 's> {
pub radio_targets: Vec<&'g Vec<Object<'s>>>, pub radio_targets: Vec<&'g Vec<Object<'s>>>,
pub file_access: &'g dyn FileAccessInterface, pub file_access: &'g dyn FileAccessInterface,
@@ -58,26 +54,6 @@ pub struct GlobalSettings<'g, 's> {
/// ///
/// Corresponds to org-entities elisp variable. /// Corresponds to org-entities elisp variable.
pub entities: &'g [EntityDefinition<'s>], pub entities: &'g [EntityDefinition<'s>],
/// Keywords that contain the standard set of objects (excluding footnote references).
///
/// Corresponds to org-element-parsed-keywords elisp variable.
pub element_parsed_keywords: &'g [&'s str],
/// Keywords that can have a secondary value in square brackets.
///
/// Corresponds to org-element-dual-keywords elisp variable.
pub element_dual_keywords: &'g [&'s str],
/// Keywords that can be affiliated with an element.
///
/// Corresponds to org-element-affiliated-keywords elisp variable.
pub element_affiliated_keywords: &'g [&'s str],
/// Mapping of keyword names.
///
/// Corresponds to org-element-keyword-translation-alist elisp variable.
pub element_keyword_translation_alist: &'g [(&'s str, &'s str)],
} }
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8; pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@@ -112,10 +88,6 @@ impl<'g, 's> GlobalSettings<'g, 's> {
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS, link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
link_templates: BTreeMap::new(), link_templates: BTreeMap::new(),
entities: &DEFAULT_ORG_ENTITIES, entities: &DEFAULT_ORG_ENTITIES,
element_parsed_keywords: &DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS,
element_dual_keywords: &DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS,
element_affiliated_keywords: &DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS,
element_keyword_translation_alist: &DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST,
} }
} }
} }
@@ -126,7 +98,7 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
} }
} }
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Clone, PartialEq, Default)]
pub enum HeadlineLevelFilter { pub enum HeadlineLevelFilter {
Odd, Odd,

View File

@@ -1,7 +1,7 @@
use crate::error::Res; use crate::error::Res;
use crate::parser::OrgSource; use crate::parser::OrgSource;
mod constants; pub(crate) mod constants;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod context; mod context;
mod exiting; mod exiting;

View File

@@ -2,22 +2,18 @@ use nom::error::ErrorKind;
use nom::error::ParseError; use nom::error::ParseError;
use nom::IResult; use nom::IResult;
pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>; pub(crate) type Res<T, U> = IResult<T, U, CustomError>;
#[derive(Debug)] #[derive(Debug)]
pub enum CustomError<I> { pub enum CustomError {
MyError(MyError<&'static str>), Static(&'static str),
Nom(I, ErrorKind),
IO(std::io::Error), IO(std::io::Error),
BoxedError(Box<dyn std::error::Error>), Parser(ErrorKind),
} }
#[derive(Debug)] impl<I: std::fmt::Debug> ParseError<I> for CustomError {
pub struct MyError<I>(pub(crate) I); fn from_error_kind(_input: I, kind: ErrorKind) -> Self {
CustomError::Parser(kind)
impl<I> ParseError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
CustomError::Nom(input, kind)
} }
fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self { fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self {
@@ -26,20 +22,14 @@ impl<I> ParseError<I> for CustomError<I> {
} }
} }
impl<I> From<std::io::Error> for CustomError<I> { impl From<std::io::Error> for CustomError {
fn from(value: std::io::Error) -> Self { fn from(value: std::io::Error) -> Self {
CustomError::IO(value) CustomError::IO(value)
} }
} }
impl<I> From<&'static str> for CustomError<I> { impl From<&'static str> for CustomError {
fn from(value: &'static str) -> Self { fn from(value: &'static str) -> Self {
CustomError::MyError(MyError(value)) CustomError::Static(value)
}
}
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
fn from(value: Box<dyn std::error::Error>) -> Self {
CustomError::BoxedError(value)
} }
} }

View File

@@ -1,5 +1,4 @@
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod error; mod error;
pub(crate) use error::CustomError; pub(crate) use error::CustomError;
pub(crate) use error::MyError;
pub(crate) use error::Res; pub(crate) use error::Res;

View File

@@ -0,0 +1,43 @@
use std::collections::HashMap;
use std::sync::Mutex;
use super::EventType;
use crate::parser::OrgSource;
#[derive(Debug, Eq, Hash, PartialEq)]
struct EventKey {
event_type: EventType,
byte_offset: usize,
}
pub(crate) type EventCount = usize;
static GLOBAL_DATA: Mutex<Option<HashMap<EventKey, EventCount>>> = Mutex::new(None);
pub(crate) fn record_event(event_type: EventType, input: OrgSource<'_>) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let key = EventKey {
event_type,
byte_offset: input.get_byte_offset(),
};
*db.entry(key).or_insert(0) += 1;
}
pub(crate) fn report(original_document: &str) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let mut results: Vec<_> = db.iter().collect();
results.sort_by_key(|(_k, v)| *v);
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));
for (key, count) in results {
println!(
"{:?} {} character offset: {} byte offset: {}",
key.event_type,
count,
original_document[..key.byte_offset].chars().count() + 1,
key.byte_offset
)
}
}

View File

@@ -0,0 +1,4 @@
#[derive(Debug, Eq, Hash, PartialEq)]
pub(crate) enum EventType {
ElementStart,
}

6
src/event_count/mod.rs Normal file
View File

@@ -0,0 +1,6 @@
mod database;
mod event_type;
pub(crate) use database::record_event;
pub(crate) use database::report;
pub(crate) use event_type::EventType;

View File

@@ -90,12 +90,11 @@ impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
} }
} }
impl<'r, 's> IntoIterator for AstNode<'r, 's> { impl<'r, 's> AstNode<'r, 's> {
type Item = AstNode<'r, 's>; /// Iterate all AST nodes.
///
type IntoIter = AllAstNodeIter<'r, 's>; /// This is different from the iter/into_iter functions which iterate a single level of the children. This iterates the entire tree including returning the root node itself.
pub fn iter_all_ast_nodes(self) -> AllAstNodeIter<'r, 's> {
fn into_iter(self) -> Self::IntoIter {
AllAstNodeIter { AllAstNodeIter {
root: Some(self), root: Some(self),
queue: VecDeque::new(), queue: VecDeque::new(),

View File

@@ -3,6 +3,8 @@
#![feature(path_file_prefix)] #![feature(path_file_prefix)]
#![feature(is_sorted)] #![feature(is_sorted)]
#![feature(test)] #![feature(test)]
#![feature(iter_intersperse)]
#![feature(exact_size_is_empty)]
// TODO: #![warn(missing_docs)] // TODO: #![warn(missing_docs)]
#![allow(clippy::bool_assert_comparison)] // Sometimes you want the long form because its easier to see at a glance. #![allow(clippy::bool_assert_comparison)] // Sometimes you want the long form because its easier to see at a glance.
@@ -10,9 +12,20 @@ extern crate test;
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
pub mod compare; pub mod compare;
pub mod parse_cli;
#[cfg(any(feature = "compare", feature = "wasm", feature = "wasm_test"))]
mod util;
#[cfg(any(feature = "wasm", feature = "wasm_test"))]
mod wasm;
#[cfg(any(feature = "wasm", feature = "wasm_test"))]
pub mod wasm_cli;
#[cfg(feature = "wasm_test")]
pub mod wasm_test;
mod context; mod context;
mod error; mod error;
#[cfg(feature = "event_count")]
mod event_count;
mod iter; mod iter;
pub mod parser; pub mod parser;
pub mod types; pub mod types;

View File

@@ -1,12 +1,4 @@
#![feature(round_char_boundary)] use organic::parse_cli::main_body;
#![feature(exact_size_is_empty)]
use std::io::Read;
use std::path::Path;
use ::organic::parser::parse;
use organic::parser::parse_with_settings;
use organic::settings::GlobalSettings;
use organic::settings::LocalFileAccessInterface;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry; use crate::init_tracing::init_telemetry;
@@ -30,50 +22,3 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
main_body_result main_body_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_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>> {
let mut stdin_contents = String::new();
std::io::stdin()
.lock()
.read_to_string(&mut stdin_contents)?;
Ok(stdin_contents)
}
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let rust_parsed = parse(org_contents.as_ref())?;
println!("{:#?}", rust_parsed);
Ok(())
}
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
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 = GlobalSettings {
file_access: &file_access_interface,
..Default::default()
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
println!("{:#?}", rust_parsed);
Ok(())
}

59
src/parse_cli/mod.rs Normal file
View File

@@ -0,0 +1,59 @@
use std::io::Read;
use std::path::Path;
use crate::parser::parse;
use crate::parser::parse_with_settings;
use crate::settings::GlobalSettings;
use crate::settings::LocalFileAccessInterface;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub 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_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>> {
let mut stdin_contents = String::new();
std::io::stdin()
.lock()
.read_to_string(&mut stdin_contents)?;
Ok(stdin_contents)
}
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = org_contents.as_ref();
let rust_parsed = parse(org_contents)?;
println!("{:#?}", rust_parsed);
#[cfg(feature = "event_count")]
crate::event_count::report(org_contents);
Ok(())
}
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
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 = GlobalSettings {
file_access: &file_access_interface,
..Default::default()
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
println!("{:#?}", rust_parsed);
#[cfg(feature = "event_count")]
crate::event_count::report(org_contents);
Ok(())
}

View File

@@ -14,17 +14,46 @@ use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::util::confine_context; use super::util::confine_context;
use super::OrgSource;
use crate::context::bind_context; use crate::context::bind_context;
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::constants::ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
use crate::context::constants::ORG_ELEMENT_PARSED_KEYWORDS;
use crate::context::Context; use crate::context::Context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::error::Res;
use crate::types::AffiliatedKeywordValue; use crate::types::AffiliatedKeywordValue;
use crate::types::AffiliatedKeywords; use crate::types::AffiliatedKeywords;
use crate::types::Keyword; use crate::types::Keyword;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keywords<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
let mut ret = Vec::new();
let mut remaining = input;
loop {
let result = affiliated_keyword(remaining);
match result {
Ok((remain, kw)) => {
remaining = remain;
ret.push(kw);
}
Err(_) => {
break;
}
}
}
Ok((remaining, ret))
}
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>( pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
input: AK, input: AK,
@@ -34,8 +63,8 @@ where
{ {
let mut ret = BTreeMap::new(); let mut ret = BTreeMap::new();
for kw in input { for kw in input {
let translated_name = translate_name(global_settings, kw.key); let translated_name = translate_name(kw.key);
let keyword_type = identify_keyword_type(global_settings, translated_name.as_str()); let keyword_type = identify_keyword_type(translated_name.as_str());
match keyword_type { match keyword_type {
AffiliatedKeywordType::SingleString => { AffiliatedKeywordType::SingleString => {
ret.insert( ret.insert(
@@ -120,12 +149,12 @@ where
AffiliatedKeywords { keywords: ret } AffiliatedKeywords { keywords: ret }
} }
fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String { fn translate_name(name: &str) -> String {
let name_until_optval = name let name_until_optval = name
.split_once('[') .split_once('[')
.map(|(before, _after)| before) .map(|(before, _after)| before)
.unwrap_or(name); .unwrap_or(name);
for (src, dst) in global_settings.element_keyword_translation_alist { for (src, dst) in ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST {
if name_until_optval.eq_ignore_ascii_case(src) { if name_until_optval.eq_ignore_ascii_case(src) {
return dst.to_lowercase(); return dst.to_lowercase();
} }
@@ -140,20 +169,15 @@ enum AffiliatedKeywordType {
ObjectTree, ObjectTree,
} }
fn identify_keyword_type<'g, 's>( fn identify_keyword_type(name: &str) -> AffiliatedKeywordType {
global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> AffiliatedKeywordType {
let is_multiple = ["CAPTION", "HEADER"] let is_multiple = ["CAPTION", "HEADER"]
.into_iter() .into_iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate)) .any(|candidate| name.eq_ignore_ascii_case(candidate))
|| name.to_lowercase().starts_with("attr_"); || name.to_lowercase().starts_with("attr_");
let is_parsed = global_settings let is_parsed = ORG_ELEMENT_PARSED_KEYWORDS
.element_parsed_keywords
.iter() .iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate)); .any(|candidate| name.eq_ignore_ascii_case(candidate));
let can_have_optval = global_settings let can_have_optval = ORG_ELEMENT_DUAL_KEYWORDS
.element_dual_keywords
.iter() .iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate)); .any(|candidate| name.eq_ignore_ascii_case(candidate));
match (is_multiple, is_parsed, can_have_optval) { match (is_multiple, is_parsed, can_have_optval) {

View File

@@ -47,7 +47,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
parser_with_context!(parse_angle_link)(context), parser_with_context!(parse_angle_link)(context),
))(remaining)?; ))(remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -59,6 +59,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
raw_link: raw_link.into(), raw_link: raw_link.into(),
search_option: parsed_link.search_option, search_option: parsed_link.search_option,
application: parsed_link.application, application: parsed_link.application,
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

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::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
@@ -21,7 +20,6 @@ use super::OrgSource;
use crate::context::Matcher; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::util::org_line_ending; use crate::parser::util::org_line_ending;
@@ -44,32 +42,10 @@ where
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?; let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
BabelCall {
source: Into::<&str>::into(source),
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(line_break.take(0)),
call: None,
inside_header: None,
arguments: None,
end_header: None,
},
));
}
let (remaining, _ws) = space0(remaining)?; let (remaining, _ws) = space0(remaining)?;
let (remaining, (value, babel_call_value)) = consumed(babel_call_value)(remaining)?; let (remaining, babel_call_value) = babel_call_value(remaining)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -81,17 +57,22 @@ where
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
), ),
value: Into::<&str>::into(value).trim_end(), value: Into::<&str>::into(babel_call_value.value),
call: babel_call_value.call.map(Into::<&str>::into), call: babel_call_value.call.map(Into::<&str>::into),
inside_header: babel_call_value.inside_header.map(Into::<&str>::into), inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
arguments: babel_call_value.arguments.map(Into::<&str>::into), arguments: babel_call_value.arguments.map(Into::<&str>::into),
end_header: babel_call_value.end_header.map(Into::<&str>::into), end_header: babel_call_value.end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
#[derive(Debug)] #[derive(Debug)]
struct BabelCallValue<'s> { struct BabelCallValue<'s> {
/// The entire string to the right of "#+call: " without the trailing line break.
value: OrgSource<'s>,
/// The function name which may contain a line break if there are no headers/arguments.
call: Option<OrgSource<'s>>, call: Option<OrgSource<'s>>,
inside_header: Option<OrgSource<'s>>, inside_header: Option<OrgSource<'s>>,
arguments: Option<OrgSource<'s>>, arguments: Option<OrgSource<'s>>,
@@ -100,13 +81,45 @@ struct BabelCallValue<'s> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> { fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, call) = opt(babel_call_call)(input)?; alt((
let (remaining, inside_header) = opt(inside_header)(remaining)?; babel_call_value_without_headers,
let (remaining, arguments) = opt(arguments)(remaining)?; babel_call_value_with_headers,
let (remaining, end_header) = opt(end_header)(remaining)?; ))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value_without_headers<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, value) = babel_call_call_with_headers(input)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let call = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
BabelCallValue { BabelCallValue {
value,
call: Some(call),
inside_header: None,
arguments: None,
end_header: None,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value_with_headers<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, call) = opt(babel_call_call_with_headers)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
let value = get_consumed(input, remaining);
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
Ok((
remaining,
BabelCallValue {
value,
call, call,
inside_header, inside_header,
arguments: arguments.flatten(), arguments: arguments.flatten(),
@@ -116,14 +129,15 @@ fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallVal
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn babel_call_call_with_headers<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// When babel call contains no arguments or headers (for example: "#+call: lorem ipsum\n") then the trailing line break is part of the call. Otherwise, it is not.
verify( verify(
recognize(many_till( recognize(many_till(
anychar, anychar,
alt(( peek(alt((
peek(recognize(one_of("[("))), recognize(one_of("[(")),
recognize(tuple((space0, org_line_ending))), recognize(tuple((space0, org_line_ending))),
)), ))),
)), )),
|s| s.len() > 0, |s| s.len() > 0,
)(input) )(input)
@@ -217,9 +231,7 @@ fn impl_balanced_bracket<
} }
if fail_parser(remaining).is_ok() { if fail_parser(remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("Fail parser matched.")));
"Fail parser matched.",
))));
} }
let (remain, _) = anychar(remaining)?; let (remain, _) = anychar(remaining)?;
@@ -228,26 +240,10 @@ fn impl_balanced_bracket<
let contents_end = remaining; let contents_end = remaining;
let (remaining, _) = end_parser(remaining)?; let (remaining, _) = end_parser(remaining)?;
let contents = if contents_start != contents_end { let contents = if Into::<&str>::into(contents_start) != Into::<&str>::into(contents_end) {
Some(contents_start.get_until(contents_end)) Some(contents_start.get_until(contents_end))
} else { } else {
None None
}; };
Ok((remaining, contents)) Ok((remaining, contents))
} }
#[cfg(test)]
mod tests {
use nom::combinator::opt;
use super::*;
#[test]
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("()");
let (remaining, call) = opt(babel_call_call)(input)?;
assert_eq!(Into::<&str>::into(remaining), "()");
assert_eq!(call, None);
Ok(())
}
}

167
src/parser/bullshitium.rs Normal file
View File

@@ -0,0 +1,167 @@
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::paragraph::paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use super::util::start_of_line;
use super::OrgSource;
use crate::context::bind_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::macros::element;
use crate::types::Object;
use crate::types::Paragraph;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn bullshitium<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
alt((
bind_context!(broken_end, context),
bind_context!(broken_dynamic_block, context),
))(input)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn detect_bullshitium<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
element!(detect_broken_end, context, input);
element!(detect_broken_dynamic_block, context, input);
Err(nom::Err::Error(CustomError::Static("No bullshitium.")))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn broken_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case(":end:")(remaining)?;
let (lead_in_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
if let Ok((remaining, mut paragraph)) =
paragraph(std::iter::empty(), lead_in_remaining, context, input)
{
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
}
None => {
unreachable!("Paragraph must have children.");
}
};
Ok((remaining, paragraph))
} else {
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn detect_broken_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case(":end:")(remaining)?;
let (_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
Ok((input, ()))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
let (lead_in_remaining, _) = many_till(anychar, org_line_ending)(remaining)?;
if let Ok((remaining, mut paragraph)) =
paragraph(std::iter::empty(), lead_in_remaining, context, input)
{
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
}
None => {
unreachable!("Paragraph must have children.");
}
};
Ok((remaining, paragraph))
} else {
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn detect_broken_dynamic_block<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (_remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
Ok((input, ()))
}

View File

@@ -46,16 +46,22 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
let (remaining, prefix) = let (remaining, prefix) =
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?; must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let contents_begin = remaining;
let (remaining, references) = let (remaining, references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?; separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let contents_end = {
let (rem, _) = opt(tag(";"))(remaining)?;
rem
};
let (remaining, suffix) = must_balance_bracket(opt(map( let (remaining, suffix) = must_balance_bracket(opt(map(
tuple((tag(";"), parser_with_context!(global_suffix)(context))), tuple((tag(";"), parser_with_context!(global_suffix)(context))),
|(_, suffix)| suffix, |(_, suffix)| suffix,
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let contents = contents_begin.get_until(contents_end);
Ok(( Ok((
remaining, remaining,
Citation { Citation {
@@ -64,6 +70,8 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
prefix: prefix.unwrap_or(Vec::new()), prefix: prefix.unwrap_or(Vec::new()),
suffix: suffix.unwrap_or(Vec::new()), suffix: suffix.unwrap_or(Vec::new()),
children: references, children: references,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -137,7 +145,7 @@ fn _global_prefix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation global prefix bracket depth.") unreachable!("Exceeded citation global prefix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -191,7 +199,7 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation global suffix bracket depth.") unreachable!("Exceeded citation global suffix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -210,12 +218,11 @@ mod tests {
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::types::CitationReference;
use crate::types::Element; use crate::types::Element;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn citation_simple() { fn citation_simple() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("[cite:@foo]"); let input = OrgSource::new("[cite:@foo]");
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
@@ -227,28 +234,33 @@ mod tests {
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
first_paragraph.get_standard_properties().get_source(),
"[cite:@foo]"
);
assert_eq!(first_paragraph.children.len(), 1); assert_eq!(first_paragraph.children.len(), 1);
assert_eq!(
first_paragraph match first_paragraph
.children .children
.get(0) .first()
.expect("Len already asserted to be 1"), .expect("Len already asserted to be 1.")
&Object::Citation(Citation { {
source: "[cite:@foo]", Object::Citation(inner) => {
style: None, assert_eq!(inner.get_source(), "[cite:@foo]");
prefix: vec![], assert_eq!(inner.children.len(), 1);
suffix: vec![], assert!(inner.prefix.is_empty());
children: vec![CitationReference { assert!(inner.suffix.is_empty());
source: "@foo", assert!(inner.style.is_none());
key: "foo", let citation_reference = inner
prefix: vec![], .children
suffix: vec![] .first()
}] .expect("Len already asserted to be 1.");
}) assert_eq!(citation_reference.get_source(), "@foo");
); assert_eq!(citation_reference.key, "foo");
assert!(citation_reference.prefix.is_empty());
assert!(citation_reference.suffix.is_empty());
}
_ => {
return Err("Child should be a citation.".into());
}
};
Ok(())
} }
} }

View File

@@ -20,7 +20,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object_parser::minimal_set_object; use crate::parser::object_parser::minimal_set_object;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
@@ -151,7 +150,7 @@ fn _key_prefix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation key prefix bracket depth.") unreachable!("Exceeded citation key prefix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -181,7 +180,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation key suffix bracket depth.") unreachable!("Exceeded citation key suffix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -199,9 +198,7 @@ where
let pre_bracket_depth = input.get_bracket_depth(); let pre_bracket_depth = input.get_bracket_depth();
let (remaining, output) = inner(input)?; let (remaining, output) = inner(input)?;
if remaining.get_bracket_depth() - pre_bracket_depth != 0 { if remaining.get_bracket_depth() - pre_bracket_depth != 0 {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("UnbalancedBrackets")));
"UnbalancedBrackets",
))));
} }
Ok((remaining, output)) Ok((remaining, output))
} }

View File

@@ -40,7 +40,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?; let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?;
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?; let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -54,6 +54,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
} else { } else {
ClockStatus::Running ClockStatus::Running
}, },
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -81,7 +82,7 @@ fn clock_timestamp<'b, 'g, 'r, 's>(
|(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)), |(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)),
), ),
map( map(
parser_with_context!(inactive_timestamp)(context), parser_with_context!(inactive_timestamp(true))(context),
|timestamp| (timestamp, None), |timestamp| (timestamp, None),
), ),
))(input) ))(input)

View File

@@ -19,7 +19,6 @@ use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
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;
@@ -35,9 +34,9 @@ pub(crate) fn comment<'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") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
let parser_context = ContextElement::Context("comment"); let parser_context = ContextElement::Context("comment");
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
@@ -47,7 +46,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
let (remaining, mut remaining_lines) = let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?; many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1); let mut value = Vec::with_capacity(remaining_lines.len() + 1);
@@ -68,6 +67,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
Comment { Comment {
source: source.into(), source: source.into(),
value, value,
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -31,7 +31,7 @@ where
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?; let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
let (remaining, _eol) = org_line_ending(remaining)?; let (remaining, _eol) = org_line_ending(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -43,6 +43,7 @@ where
affiliated_keywords, affiliated_keywords,
), ),
value: Into::<&str>::into(value), value: Into::<&str>::into(value),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::opt; use nom::combinator::opt;
use nom::multi::many0; use nom::multi::many0;
use nom::InputTake;
use super::headline::heading; use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings; use super::in_buffer_settings::apply_in_buffer_settings;
@@ -19,9 +20,7 @@ use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::org_source::convert_error;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::Document; use crate::types::Document;
@@ -103,7 +102,7 @@ pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO". /// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)] #[allow(dead_code)]
fn document<'s>(context: RefContext<'_, '_, '_, 's>, input: &'s str) -> Res<&'s str, Document<'s>> { fn document<'s>(context: RefContext<'_, '_, '_, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
let (remaining, doc) = document_org_source(context, input.into()).map_err(convert_error)?; let (remaining, doc) = document_org_source(context, input.into())?;
Ok((Into::<&str>::into(remaining), doc)) Ok((Into::<&str>::into(remaining), doc))
} }
@@ -127,27 +126,16 @@ fn document_org_source<'b, 'g, 'r, 's>(
.get_global_settings() .get_global_settings()
.file_access .file_access
.read_file(setup_file) .read_file(setup_file)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into())) .map_err(|err| nom::Err::<CustomError>::Failure(err.into()))
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
for setup_file in setup_files.iter().map(String::as_str) { for setup_file in setup_files.iter().map(String::as_str) {
let (_, setup_file_settings) = let (_, setup_file_settings) = scan_for_in_buffer_settings(setup_file.into())?;
scan_for_in_buffer_settings(setup_file.into()).map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it.",
)))
})?;
final_settings.extend(setup_file_settings); final_settings.extend(setup_file_settings);
} }
final_settings.extend(document_settings); final_settings.extend(document_settings);
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings()) let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
.map_err(|err| { .map_err(nom::Err::Error)?;
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it.",
)))
})?;
let new_context = context.with_global_settings(&new_settings); let new_context = context.with_global_settings(&new_settings);
let context = &new_context; let context = &new_context;
@@ -156,7 +144,7 @@ fn document_org_source<'b, 'g, 'r, 's>(
{ {
// 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<'_>>> = Into::<AstNode>::into(&document) let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
.into_iter() .iter_all_ast_nodes()
.filter_map(|ast_node| { .filter_map(|ast_node| {
if let AstNode::RadioTarget(ast_node) = ast_node { if let AstNode::RadioTarget(ast_node) = ast_node {
Some(ast_node) Some(ast_node)
@@ -172,15 +160,13 @@ fn document_org_source<'b, 'g, 'r, 's>(
let parser_context = context.with_global_settings(&new_global_settings); let parser_context = context.with_global_settings(&new_global_settings);
let (remaining, mut document) = _document(&parser_context, input) let (remaining, mut document) = _document(&parser_context, input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))?; .map(|(rem, out)| (Into::<&str>::into(rem), out))?;
apply_post_parse_in_buffer_settings(&mut document) apply_post_parse_in_buffer_settings(&mut document);
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
return Ok((remaining.into(), document)); return Ok((remaining.into(), document));
} }
} }
// Find final in-buffer settings that do not impact parsing // Find final in-buffer settings that do not impact parsing
apply_post_parse_in_buffer_settings(&mut document) apply_post_parse_in_buffer_settings(&mut document);
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
Ok((remaining.into(), document)) Ok((remaining.into(), document))
} }
@@ -196,8 +182,10 @@ fn _document<'b, 'g, 'r, 's>(
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context); let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading(0))(context); let heading_matcher = parser_with_context!(heading(0))(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?; let (remaining, _blank_lines) = many0(blank_line)(input)?;
let contents_begin = remaining;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?; let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?; let (remaining, children) = many0(heading_matcher)(remaining)?;
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -207,6 +195,25 @@ fn _document<'b, 'g, 'r, 's>(
path: None, path: None,
zeroth_section, zeroth_section,
children, children,
contents: if contents.len() > 0 {
Into::<&str>::into(contents)
} else {
Into::<&str>::into(remaining.take(0))
},
}, },
)) ))
} }
#[cfg(test)]
mod tests {
use test::Bencher;
use super::*;
#[bench]
fn bench_full_document(b: &mut Bencher) {
let input = include_str!("../../org_mode_samples/element_container_priority/README.org");
b.iter(|| assert!(parse(input).is_ok()));
}
}

View File

@@ -4,6 +4,7 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while; use nom::bytes::complete::take_while;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::recognize; use nom::combinator::recognize;
@@ -12,17 +13,17 @@ use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass; use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::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;
@@ -31,8 +32,6 @@ use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::Drawer; use crate::types::Drawer;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
@@ -48,9 +47,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
if immediate_in_section(context, "drawer") { if immediate_in_section(context, "drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?; let (remaining, _leading_whitespace) = space0(remaining)?;
@@ -72,30 +71,12 @@ where
let parser_context = context.with_additional_node(&contexts[0]); 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[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, (contents, children)) =
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, children) = match tuple((
not(exit_matcher),
blank_line,
many_till(blank_line, exit_matcher),
))(remaining)
{
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source.into());
(remain, vec![element])
}
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, _end) = drawer_end(&parser_context, remaining)?; let (remaining, _end) = drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -109,10 +90,34 @@ where
), ),
drawer_name: drawer_name.into(), drawer_name: drawer_name.into(),
children, children,
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Element<'s>>> {
let element_matcher = parser_with_context!(element(true))(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
if let Ok((remaining, (_not_exit, empty_para))) =
tuple((not(exit_matcher), bind_context!(empty_paragraph, context)))(input)
{
return Ok((remaining, vec![Element::Paragraph(empty_para)]));
}
let (remaining, (children, _exit_contents)) = many_till(element_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input) take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)

View File

@@ -6,30 +6,28 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; 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::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::greater_block::leading_blank_lines_end;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass; use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::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;
@@ -37,8 +35,6 @@ use crate::parser::util::start_of_line;
use crate::types::DynamicBlock; use crate::types::DynamicBlock;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
@@ -54,9 +50,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
if immediate_in_section(context, "dynamic block") { if immediate_in_section(context, "dynamic block") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
start_of_line(remaining)?; start_of_line(remaining)?;
@@ -83,25 +79,25 @@ where
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);
not(exit_matcher)(remaining)?; not(exit_matcher)(remaining)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple(( let contents_begin = remaining;
blank_line, let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
many0(preceded(not(exit_matcher), blank_line)), class: ExitClass::Alpha,
))))(remaining)?; exit_matcher: &leading_blank_lines_end,
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
element.set_source(source.into());
element
}); });
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) = let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines { if let Some(lines) = leading_blank_lines {
children.insert(0, lines); children.insert(0, Element::Paragraph(lines));
} }
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?; let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -115,6 +111,12 @@ where
block_name: name.into(), block_name: name.into(),
parameters: parameters.map(|val| val.into()), parameters: parameters.map(|val| val.into()),
children, children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -1,5 +1,3 @@
use nom::multi::many0;
use super::babel_call::babel_call; use super::babel_call::babel_call;
use super::clock::clock; use super::clock::clock;
use super::comment::comment; use super::comment::comment;
@@ -14,7 +12,6 @@ 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::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;
@@ -27,11 +24,16 @@ 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::table::detect_table; use super::table::detect_table;
use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
#[cfg(feature = "event_count")]
use crate::event_count::record_event;
#[cfg(feature = "event_count")]
use crate::event_count::EventType;
use crate::parser::affiliated_keyword::affiliated_keywords;
use crate::parser::bullshitium::bullshitium;
use crate::parser::bullshitium::detect_bullshitium;
use crate::parser::macros::ak_element; use crate::parser::macros::ak_element;
use crate::parser::macros::element; use crate::parser::macros::element;
use crate::parser::table::org_mode_table; use crate::parser::table::org_mode_table;
@@ -55,8 +57,9 @@ fn _element<'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>> {
let (post_affiliated_keywords_input, affiliated_keywords) = #[cfg(feature = "event_count")]
many0(parser_with_context!(affiliated_keyword)(context))(input)?; record_event(EventType::ElementStart, input);
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@@ -240,6 +243,9 @@ fn _element<'b, 'g, 'r, 's>(
); );
if can_be_paragraph { if can_be_paragraph {
// Fake paragraphs
element!(bullshitium, context, input, Element::Paragraph);
// Paragraph without affiliated keyword // Paragraph without affiliated keyword
ak_element!( ak_element!(
paragraph, paragraph,
@@ -251,9 +257,7 @@ fn _element<'b, 'g, 'r, 's>(
); );
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No element.")))
"No element.",
))))
} }
pub(crate) const fn detect_element( pub(crate) const fn detect_element(
@@ -272,8 +276,7 @@ fn _detect_element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let (post_affiliated_keywords_input, affiliated_keywords) = let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
many0(parser_with_context!(affiliated_keyword)(context))(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@@ -319,11 +322,14 @@ fn _detect_element<'b, 'g, 'r, 's>(
input input
); );
// Fake paragraphs
if !can_be_paragraph {
element!(detect_bullshitium, context, input);
}
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {
return Ok((input, ())); return Ok((input, ()));
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No element detected.")))
"No element detected.",
))))
} }

View File

@@ -1,11 +1,11 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::satisfy; use nom::character::complete::satisfy;
use nom::combinator::cond;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@@ -13,7 +13,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::EntityDefinition; use crate::context::EntityDefinition;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::types::Entity; use crate::types::Entity;
@@ -29,7 +28,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
let (remaining, (entity_definition, entity_name, use_brackets)) = name(context, remaining)?; let (remaining, (entity_definition, entity_name, use_brackets)) = name(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -44,6 +43,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
ascii: entity_definition.ascii, ascii: entity_definition.ascii,
utf8: entity_definition.utf8, utf8: entity_definition.utf8,
use_brackets, use_brackets,
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -58,18 +58,21 @@ fn name<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> { ) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> {
for entity in context.get_global_settings().entities { for entity in context.get_global_settings().entities {
let result = tuple(( let result = tuple((
tag::<_, _, CustomError<_>>(entity.name), tag::<_, _, CustomError>(entity.name),
cond(
!entity.name.ends_with(' '),
alt(( alt((
verify(map(tag("{}"), |_| true), |_| !entity.name.ends_with(' ')), map(tag("{}"), |_| true),
map(peek(recognize(entity_end)), |_| false), map(peek(recognize(entity_end)), |_| false),
)), )),
),
))(input); ))(input);
if let Ok((remaining, (ent, use_brackets))) = result { if let Ok((remaining, (ent, use_brackets))) = result {
return Ok((remaining, (entity, ent, use_brackets))); return Ok((remaining, (entity, ent, use_brackets.unwrap_or(false))));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError("NoEntity")))) Err(nom::Err::Error(CustomError::Static("NoEntity")))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -39,7 +39,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
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) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -48,6 +48,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
source: source.into(), source: source.into(),
backend: backend_name.into(), backend: backend_name.into(),
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()), contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -2,12 +2,15 @@ use nom::branch::alt;
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::character::complete::space0;
use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@@ -35,28 +38,25 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's, AK>(
where where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context); let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?; let (remaining, first_line) = fixed_width_area_line(remaining)?;
let (remaining, mut remaining_lines) = let (remaining, remaining_lines) = many0(preceded(
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?; not(tuple((org_line_ending, exit_matcher))),
map(
tuple((org_line_ending, fixed_width_area_line)),
|(_line_ending, line_contents)| line_contents,
),
))(remaining)?;
let (remaining, _trailing_ws) = let post_blank_begin = remaining;
let (remaining, _first_line_break) = org_line_ending(remaining)?;
let (remaining, _additional_post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1); let mut value = Vec::with_capacity(remaining_lines.len() + 1);
let last_line = remaining_lines.pop();
if let Some(last_line) = last_line {
value.push(Into::<&str>::into(first_line)); value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into)); value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
let last_line = Into::<&str>::into(last_line);
// Trim the line ending from the final line.
value.push(&last_line[..(last_line.len() - 1)])
} else {
// Trim the line ending from the only line.
let only_line = Into::<&str>::into(first_line);
value.push(&only_line[..(only_line.len() - 1)])
}
Ok(( Ok((
remaining, remaining,
FixedWidthArea { FixedWidthArea {
@@ -66,25 +66,24 @@ where
affiliated_keywords, affiliated_keywords,
), ),
value, value,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
}, },
)) ))
} }
#[cfg_attr( #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
feature = "tracing", fn fixed_width_area_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
tracing::instrument(ret, level = "debug", skip(_context))
)]
fn fixed_width_area_line<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _) = tuple((space0, tag(":")))(input)?; let (remaining, _) = tuple((space0, tag(":")))(input)?;
if let Ok((remaining, line_break)) = org_line_ending(remaining) { if let Ok((_remain, _line_break)) = org_line_ending(remaining) {
return Ok((remaining, line_break)); return Ok((remaining, remaining.take(0)));
} }
let (remaining, _) = tag(" ")(remaining)?; let (remaining, _) = tag(" ")(remaining)?;
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?; let (remaining, value) = recognize(many_till(anychar, peek(org_line_ending)))(remaining)?;
Ok((remaining, value)) Ok((remaining, value))
} }

View File

@@ -22,7 +22,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::util::blank_line; use crate::parser::util::blank_line;
@@ -48,9 +47,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
if immediate_in_section(context, "footnote definition") { if immediate_in_section(context, "footnote definition") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
start_of_line(remaining)?; start_of_line(remaining)?;
// Cannot be indented. // Cannot be indented.
@@ -76,6 +75,7 @@ where
let parser_context = parser_context.with_additional_node(&contexts[2]); 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 before_contents = remaining;
let (mut remaining, (mut children, _exit_contents)) = let (mut remaining, (mut children, _exit_contents)) =
many_till(include_input(element_matcher), exit_matcher)(remaining)?; many_till(include_input(element_matcher), exit_matcher)(remaining)?;
@@ -91,13 +91,16 @@ where
} }
} }
let (remaining, _trailing_ws) = let contents = get_consumed(before_contents, remaining);
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
FootnoteDefinition { FootnoteDefinition {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
affiliated_keywords: parse_affiliated_keywords( affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
@@ -161,7 +164,7 @@ mod tests {
use crate::context::Context; use crate::context::Context;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
@@ -182,17 +185,13 @@ line footnote.",
footnote_definition_matcher(remaining).expect("Parse second footnote_definition."); footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(
first_footnote_definition first_footnote_definition.get_source(),
.get_standard_properties()
.get_source(),
"[fn:1] A footnote. "[fn:1] A footnote.
" "
); );
assert_eq!( assert_eq!(
second_footnote_definition second_footnote_definition.get_source(),
.get_standard_properties()
.get_source(),
"[fn:2] A multi- "[fn:2] A multi-
line footnote." line footnote."
@@ -217,9 +216,7 @@ not in the footnote.",
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");
assert_eq!(Into::<&str>::into(remaining), "not in the footnote."); assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
assert_eq!( assert_eq!(
first_footnote_definition first_footnote_definition.get_source(),
.get_standard_properties()
.get_source(),
"[fn:2] A multi- "[fn:2] A multi-
line footnote. line footnote.

View File

@@ -2,6 +2,7 @@ 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::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::consumed;
use nom::combinator::map_parser; use nom::combinator::map_parser;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many1; use nom::multi::many1;
@@ -20,7 +21,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
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;
@@ -60,7 +60,7 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context)); let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser( let (remaining, (contents, children)) = consumed(map_parser(
verify( verify(
parser_with_context!(text_until_exit)(&parser_context), parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0, |text| text.len() > 0,
@@ -70,17 +70,19 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
&initial_context, &initial_context,
)))(i) )))(i)
}), }),
)(remaining)?; ))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; 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,
FootnoteReference { FootnoteReference {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
label: None, label: None,
definition: children, definition: children,
}, },
@@ -107,7 +109,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context)); let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser( let (remaining, (contents, children)) = consumed(map_parser(
verify( verify(
parser_with_context!(text_until_exit)(&parser_context), parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0, |text| text.len() > 0,
@@ -117,17 +119,19 @@ fn inline_footnote<'b, 'g, 'r, 's>(
&initial_context, &initial_context,
)))(i) )))(i)
}), }),
)(remaining)?; ))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; 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,
FootnoteReference { FootnoteReference {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
label: Some(label_contents.into()), label: Some(label_contents.into()),
definition: children, definition: children,
}, },
@@ -145,13 +149,15 @@ fn footnote_reference_only<'b, 'g, 'r, '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, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; 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,
FootnoteReference { FootnoteReference {
source: source.into(), source: source.into(),
contents: None,
post_blank: post_blank.map(Into::<&str>::into),
label: Some(label_contents.into()), label: Some(label_contents.into()),
definition: Vec::with_capacity(0), definition: Vec::with_capacity(0),
}, },
@@ -176,9 +182,9 @@ fn _footnote_definition_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep // Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"NoFootnoteReferenceDefinitionEnd", "NoFootnoteReferenceDefinitionEnd",
)))); )));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition. // This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.

View File

@@ -5,22 +5,21 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; 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::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::in_section; use super::util::in_section;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ContextMatcher; use crate::context::ContextMatcher;
@@ -28,7 +27,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::util::blank_line; use crate::parser::util::blank_line;
@@ -38,9 +36,7 @@ use crate::parser::util::start_of_line;
use crate::types::CenterBlock; use crate::types::CenterBlock;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::QuoteBlock; use crate::types::QuoteBlock;
use crate::types::SetSource;
use crate::types::SpecialBlock; use crate::types::SpecialBlock;
#[cfg_attr( #[cfg_attr(
@@ -104,7 +100,7 @@ fn center_block<'b, 'g, 'r, 's, AK>(
where where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
let (remaining, (source, children)) = greater_block_body( let (remaining, body) = greater_block_body(
context, context,
input, input,
pre_affiliated_keywords_input, pre_affiliated_keywords_input,
@@ -114,12 +110,14 @@ where
Ok(( Ok((
remaining, remaining,
Element::CenterBlock(CenterBlock { Element::CenterBlock(CenterBlock {
source, source: body.source,
affiliated_keywords: parse_affiliated_keywords( affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
), ),
children, children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}), }),
)) ))
} }
@@ -137,7 +135,7 @@ fn quote_block<'b, 'g, 'r, 's, AK>(
where where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
let (remaining, (source, children)) = greater_block_body( let (remaining, body) = greater_block_body(
context, context,
input, input,
pre_affiliated_keywords_input, pre_affiliated_keywords_input,
@@ -147,12 +145,14 @@ where
Ok(( Ok((
remaining, remaining,
Element::QuoteBlock(QuoteBlock { Element::QuoteBlock(QuoteBlock {
source, source: body.source,
affiliated_keywords: parse_affiliated_keywords( affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
), ),
children, children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}), }),
)) ))
} }
@@ -198,7 +198,7 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?; let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) = greater_block_body( let (remaining, body) = greater_block_body(
context, context,
remaining, remaining,
pre_affiliated_keywords_input, pre_affiliated_keywords_input,
@@ -208,18 +208,28 @@ where
Ok(( Ok((
remaining, remaining,
Element::SpecialBlock(SpecialBlock { Element::SpecialBlock(SpecialBlock {
source, source: body.source,
affiliated_keywords: parse_affiliated_keywords( affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
), ),
children, children: body.children,
block_type: name, block_type: name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)), parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
contents: body.contents,
post_blank: body.post_blank,
}), }),
)) ))
} }
#[derive(Debug)]
struct GreaterBlockBody<'s> {
source: &'s str,
children: Vec<Element<'s>>,
contents: Option<&'s str>,
post_blank: Option<&'s str>,
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context))
@@ -230,11 +240,11 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
pre_affiliated_keywords_input: OrgSource<'s>, pre_affiliated_keywords_input: OrgSource<'s>,
name: &'c str, name: &'c str,
context_name: &'c str, context_name: &'c str,
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> { ) -> Res<OrgSource<'s>, GreaterBlockBody<'s>> {
if in_section(context, context_name) { if in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
let exit_with_name = greater_block_end(name); let exit_with_name = greater_block_end(name);
let (remaining, _nl) = tuple((space0, line_ending))(input)?; let (remaining, _nl) = tuple((space0, line_ending))(input)?;
@@ -252,30 +262,43 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
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);
not(exit_matcher)(remaining)?; not(exit_matcher)(remaining)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple(( let contents_begin = remaining;
blank_line,
many0(preceded(not(exit_matcher), blank_line)), let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
))))(remaining)?; class: ExitClass::Alpha,
let leading_blank_lines = exit_matcher: &leading_blank_lines_end,
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
element.set_source(source.into());
element
}); });
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) = let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines { if let Some(lines) = leading_blank_lines {
children.insert(0, lines); children.insert(0, Element::Paragraph(lines));
} }
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = exit_with_name(&parser_context, remaining)?; let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block // Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(pre_affiliated_keywords_input, remaining); let source = get_consumed(pre_affiliated_keywords_input, remaining);
Ok((remaining, (Into::<&str>::into(source), children))) Ok((
remaining,
GreaterBlockBody {
source: Into::<&str>::into(source),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -311,3 +334,14 @@ fn _greater_block_end<'b, 'g, 'r, 's, 'c>(
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", skip(_context))
)]
pub(crate) fn leading_blank_lines_end<'b, 'g, 'r, 's, 'c>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(not(blank_line))(input)
}

View File

@@ -18,18 +18,18 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::section::section; use super::section::section;
use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::org_line_ending; use super::util::org_line_ending;
use super::util::org_space; use super::util::org_space;
use super::util::org_space_or_line_ending; use super::util::org_space_or_line_ending;
use super::util::start_of_line; use super::util::start_of_line;
use crate::context::parser_with_context; use crate::context::bind_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass; use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -62,10 +62,12 @@ fn _heading<'b, 'g, 'r, 's>(
let mut scheduled = None; let mut scheduled = None;
let mut deadline = None; let mut deadline = None;
let mut closed = None; let mut closed = None;
not(|i| context.check_exit_matcher(i))(input)?; not(bind_context!(exit_matcher_parser, context))(input)?;
let (remaining, pre_headline) = headline(context, input, parent_star_count)?; let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = parser_with_context!(section)(context); let section_matcher = bind_context!(section, context);
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context); let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
let (contents_begin, _) = opt(many0(blank_line))(remaining)?;
let maybe_post_blank = get_consumed(remaining, contents_begin);
let (remaining, maybe_section) = let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?; opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?; let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
@@ -82,7 +84,8 @@ fn _heading<'b, 'g, 'r, 's>(
} }
children.insert(0, section); children.insert(0, section);
} }
let remaining = if children.is_empty() { let has_children = !children.is_empty();
let remaining = if !has_children {
// Support empty headings // Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?; let (remain, _ws) = many0(blank_line)(remaining)?;
remain remain
@@ -91,6 +94,7 @@ fn _heading<'b, 'g, 'r, 's>(
}; };
let is_archived = pre_headline.tags.contains(&"ARCHIVE"); let is_archived = pre_headline.tags.contains(&"ARCHIVE");
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -112,6 +116,16 @@ fn _heading<'b, 'g, 'r, 's>(
scheduled, scheduled,
deadline, deadline,
closed, closed,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: if has_children {
None
} else {
Some(Into::<&str>::into(maybe_post_blank))
},
}, },
)) ))
} }
@@ -155,7 +169,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, (_, (headline_level, star_count, _), _)) = tuple(( let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
start_of_line, start_of_line,
verify( verify(
parser_with_context!(headline_level)(&parser_context), bind_context!(headline_level, &parser_context),
|(_, count, _)| *count > parent_star_count, |(_, count, _)| *count > parent_star_count,
), ),
peek(org_space), peek(org_space),
@@ -163,7 +177,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_todo_keyword) = opt(tuple(( let (remaining, maybe_todo_keyword) = opt(tuple((
space1, space1,
parser_with_context!(heading_keyword)(&parser_context), bind_context!(heading_keyword, &parser_context),
peek(org_space_or_line_ending), peek(org_space_or_line_ending),
)))(remaining)?; )))(remaining)?;
@@ -177,9 +191,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_title) = opt(tuple(( let (remaining, maybe_title) = opt(tuple((
space1, space1,
consumed(many1(parser_with_context!(standard_set_object)( consumed(many1(bind_context!(standard_set_object, &parser_context))),
&parser_context,
))),
)))(remaining)?; )))(remaining)?;
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?; let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
@@ -260,7 +272,7 @@ fn heading_keyword<'b, 'g, 'r, 's>(
.iter() .iter()
.map(String::as_str) .map(String::as_str)
{ {
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); let result = tag::<_, _, CustomError>(todo_keyword)(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
return Ok((remaining, (TodoKeywordType::Todo, ent))); return Ok((remaining, (TodoKeywordType::Todo, ent)));
} }
@@ -270,14 +282,12 @@ fn heading_keyword<'b, 'g, 'r, 's>(
.iter() .iter()
.map(String::as_str) .map(String::as_str)
{ {
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); let result = tag::<_, _, CustomError>(todo_keyword)(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
return Ok((remaining, (TodoKeywordType::Done, ent))); return Ok((remaining, (TodoKeywordType::Done, ent)));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoTodoKeyword")))
"NoTodoKeyword",
))))
} }
} }
@@ -288,9 +298,9 @@ fn priority_cookie(input: OrgSource<'_>) -> Res<OrgSource<'_>, PriorityCookie> {
tag("]"), tag("]"),
))(input)?; ))(input)?;
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| { let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
nom::Err::Error(CustomError::MyError(MyError( nom::Err::Error(CustomError::Static(
"Failed to cast priority cookie to number.", "Failed to cast priority cookie to number.",
))) ))
})?; })?;
Ok((remaining, cookie)) Ok((remaining, cookie))
} }

View File

@@ -38,7 +38,7 @@ where
space0, space0,
alt((line_ending, eof)), alt((line_ending, eof)),
)))(remaining)?; )))(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -49,6 +49,7 @@ where
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
), ),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -35,7 +35,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
let mut remaining = input; let mut remaining = input;
loop { loop {
// Skip text until possible in_buffer_setting // Skip text until possible in_buffer_setting
let start_of_pound = take_until::<_, _, CustomError<_>>("#+")(remaining); let start_of_pound = take_until::<_, _, CustomError>("#+")(remaining);
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound { let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
start_of_pound start_of_pound
} else { } else {
@@ -47,7 +47,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) { let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
Ok((remain, kw)) => (remain, Some(kw)), Ok((remain, kw)) => (remain, Some(kw)),
Err(_) => { Err(_) => {
let end_of_line = take_until::<_, _, CustomError<_>>("\n")(start_of_pound); let end_of_line = take_until::<_, _, CustomError>("\n")(start_of_pound);
if let Ok((end_of_line, _)) = end_of_line { if let Ok((end_of_line, _)) = end_of_line {
(end_of_line, None) (end_of_line, None)
} else { } else {
@@ -84,11 +84,14 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
))(input) ))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] #[cfg_attr(
feature = "tracing",
tracing::instrument(level = "debug", skip(original_settings))
)]
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>( pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
keywords: Vec<Keyword<'sf>>, keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>, original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, String> { ) -> Result<GlobalSettings<'g, 's>, CustomError> {
let mut new_settings = original_settings.clone(); let mut new_settings = original_settings.clone();
// Todo Keywords // Todo Keywords
@@ -98,7 +101,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|| kw.key.eq_ignore_ascii_case("typ_todo") || kw.key.eq_ignore_ascii_case("typ_todo")
}) { }) {
let (_, (in_progress_words, complete_words)) = let (_, (in_progress_words, complete_words)) =
todo_keywords(kw.value).map_err(|err| err.to_string())?; todo_keywords(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
new_settings new_settings
.in_progress_todo_keywords .in_progress_todo_keywords
.extend(in_progress_words.into_iter().map(str::to_string)); .extend(in_progress_words.into_iter().map(str::to_string));
@@ -112,9 +119,14 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
.iter() .iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("startup")) .filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
{ {
let (_remaining, settings) = let (_remaining, settings) = separated_list0(space1::<&str, CustomError>, is_not(" \t"))(
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value) kw.value,
.map_err(|err: nom::Err<_>| err.to_string())?; )
.map_err(|err: nom::Err<_>| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
if settings.contains(&"odd") { if settings.contains(&"odd") {
new_settings.odd_levels_only = HeadlineLevelFilter::Odd; new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
} }
@@ -128,7 +140,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
.iter() .iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("link")) .filter(|kw| kw.key.eq_ignore_ascii_case("link"))
{ {
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|e| e.to_string())?; let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
new_settings new_settings
.link_templates .link_templates
.insert(link_key.to_owned(), link_value.to_owned()); .insert(link_key.to_owned(), link_value.to_owned());
@@ -139,11 +155,9 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing. /// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>( pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
document: &mut Document<'s>,
) -> Result<(), &'static str> {
document.category = Into::<AstNode>::into(&*document) document.category = Into::<AstNode>::into(&*document)
.into_iter() .iter_all_ast_nodes()
.filter_map(|ast_node| { .filter_map(|ast_node| {
if let AstNode::Keyword(ast_node) = ast_node { if let AstNode::Keyword(ast_node) = ast_node {
if ast_node.key.eq_ignore_ascii_case("category") { if ast_node.key.eq_ignore_ascii_case("category") {
@@ -154,7 +168,6 @@ pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
}) })
.last() .last()
.map(|kw| kw.value.to_owned()); .map(|kw| kw.value.to_owned());
Ok(())
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -19,7 +19,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
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;
@@ -39,7 +38,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
let (remaining, arguments) = argument(context, remaining)?; let (remaining, arguments) = argument(context, remaining)?;
let (remaining, end_header) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, end_header) = opt(parser_with_context!(header)(context))(remaining)?;
let value = get_consumed(input, remaining); let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -55,6 +54,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
None None
}, },
end_header: end_header.map(Into::<&str>::into), end_header: end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -131,9 +131,7 @@ fn _header_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep // Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
"NoHeaderEnd",
))));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header. // This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
@@ -183,9 +181,7 @@ fn _argument_end<'b, 'g, 'r, 's>(
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth; let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep // Its impossible for the next character to end the argument if we're any amount of parenthesis deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("NoArgumentEnd")));
"NoArgumentEnd",
))));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument. // This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.

View File

@@ -21,7 +21,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
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;
@@ -39,7 +38,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
let (remaining, language) = lang(context, remaining)?; let (remaining, language) = lang(context, remaining)?;
let (remaining, parameters) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, parameters) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, value) = body(context, remaining)?; let (remaining, value) = body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -49,6 +48,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
language: language.into(), language: language.into(),
parameters: parameters.map(Into::<&str>::into), parameters: parameters.map(Into::<&str>::into),
value: value.into(), value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -125,9 +125,7 @@ fn _header_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep // Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
"NoHeaderEnd",
))));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header. // This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
@@ -187,7 +185,7 @@ fn _body_end<'b, 'g, 'r, 's>(
let current_depth = input.get_brace_depth() - starting_brace_depth; let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the body if we're any amount of brace deep // Its impossible for the next character to end the body if we're any amount of brace deep
return Err(nom::Err::Error(CustomError::MyError(MyError("NoBodyEnd")))); return Err(nom::Err::Error(CustomError::Static("NoBodyEnd")));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the body. // This shouldn't be possible because if depth is 0 then a closing brace should end the body.

View File

@@ -4,11 +4,9 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; 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::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::consumed; use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::peek; use nom::combinator::peek;
@@ -22,10 +20,11 @@ use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
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 crate::context::parser_with_context; use super::util::org_line_ending;
use crate::context::constants::ORG_ELEMENT_AFFILIATED_KEYWORDS;
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::macros::element; use crate::parser::macros::element;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
@@ -50,11 +49,8 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
// 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, (consumed_input, (_, _, parsed_key, _))) = let (remaining, (consumed_input, (_, _, parsed_key, _))) =
consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?; consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?;
if let Ok((remaining, _)) = tuple(( let (remaining, _ws) = space0(remaining)?;
space0::<OrgSource<'_>, CustomError<OrgSource<'_>>>, if let Ok((remaining, _)) = org_line_ending(remaining) {
alt((line_ending, eof)),
))(remaining)
{
return Ok(( return Ok((
remaining, remaining,
Keyword { Keyword {
@@ -62,15 +58,13 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords. affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(), key: parsed_key.into(),
value: "", value: "",
post_blank: None,
}, },
)); ));
} }
let (remaining, _ws) = space0(remaining)?; let (remaining, parsed_value) =
let (remaining, parsed_value) = recognize(many_till( recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?;
anychar, let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
peek(tuple((space0, alt((line_ending, eof))))),
))(remaining)?;
let (remaining, _ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
Ok(( Ok((
remaining, remaining,
Keyword { Keyword {
@@ -78,6 +72,7 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords. affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(), key: parsed_key.into(),
value: parsed_value.into(), value: parsed_value.into(),
post_blank: None,
}, },
)) ))
} }
@@ -96,21 +91,19 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?; let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
kw.affiliated_keywords = kw.affiliated_keywords =
parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords); parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords);
kw.source = Into::<&str>::into(source); kw.source = Into::<&str>::into(source);
kw.post_blank = post_blank.map(Into::<&str>::into);
Ok((remaining, kw)) Ok((remaining, kw))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>( pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
context: RefContext<'b, 'g, 'r, 's>, filtered_keyword(affiliated_key)(input)
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(parser_with_context!(affiliated_key)(context))(input)
} }
#[cfg_attr( #[cfg_attr(
@@ -145,29 +138,18 @@ fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn affiliated_key<'b, 'g, 'r, 's>( fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, element!(dual_affiliated_key, input);
input: OrgSource<'s>, element!(plain_affiliated_key, input);
) -> Res<OrgSource<'s>, OrgSource<'s>> {
element!(dual_affiliated_key, context, input);
element!(plain_affiliated_key, context, input);
element!(export_keyword, input); element!(export_keyword, input);
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No affiliated key.")))
"No affiliated key.",
))))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_affiliated_key<'b, 'g, 'r, 's>( fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in context.get_global_settings().element_affiliated_keywords {
let result = map( let result = map(
tuple(( tuple((tag_no_case::<_, _, CustomError>(keyword), peek(tag(":")))),
tag_no_case::<_, _, CustomError<_>>(*keyword),
peek(tag(":")),
)),
|(key, _)| key, |(key, _)| key,
)(input); )(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
@@ -175,19 +157,14 @@ fn plain_affiliated_key<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
"NoKeywordKey",
))))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dual_affiliated_key<'b, 'g, 'r, 's>( fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in context.get_global_settings().element_dual_keywords {
let result = recognize(tuple(( let result = recognize(tuple((
tag_no_case::<_, _, CustomError<_>>(*keyword), tag_no_case::<_, _, CustomError>(keyword),
tag("["), tag("["),
optval, optval,
tag("]"), tag("]"),
@@ -198,9 +175,7 @@ fn dual_affiliated_key<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
"NoKeywordKey",
))))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -228,7 +203,7 @@ fn _optval_end<'s>(
unreachable!("Exceeded optval bracket depth.") unreachable!("Exceeded optval bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<_, _, CustomError<_>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -249,19 +224,12 @@ mod tests {
use test::Bencher; use test::Bencher;
use super::*; use super::*;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::OrgSource; use crate::parser::OrgSource;
#[bench] #[bench]
fn bench_affiliated_keyword(b: &mut Bencher) { fn bench_affiliated_keyword(b: &mut Bencher) {
let input = OrgSource::new("#+CAPTION[*foo*]: bar *baz*"); let input = OrgSource::new("#+CAPTION[*foo*]: bar *baz*");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
b.iter(|| assert!(affiliated_keyword(&initial_context, input).is_ok())); b.iter(|| assert!(affiliated_keyword(input).is_ok()));
} }
} }

View File

@@ -57,7 +57,7 @@ where
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?; let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let value_end = remaining; let value_end = remaining;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let value = get_consumed(value_start, value_end); let value = get_consumed(value_start, value_end);
@@ -70,6 +70,7 @@ where
affiliated_keywords, affiliated_keywords,
), ),
value: value.into(), value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -17,7 +17,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
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;
@@ -40,7 +39,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
parser_with_context!(bordered_dollar_fragment)(context), parser_with_context!(bordered_dollar_fragment)(context),
))(input)?; ))(input)?;
let value = get_consumed(input, remaining); let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -48,6 +47,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
LatexFragment { LatexFragment {
source: source.into(), source: source.into(),
value: value.into(), value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -209,9 +209,9 @@ fn pre<'b, 'g, 'r, 's>(
) -> Res<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::Static(
"Not a valid pre character for dollar char fragment.", "Not a valid pre character for dollar char fragment.",
)))); )));
} }
Ok((input, ())) Ok((input, ()))
} }
@@ -283,9 +283,9 @@ fn close_border<'b, 'g, 'r, 's>(
match preceding_character { match preceding_character {
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())), Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
_ => { _ => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for dollar char fragment.", "Not a valid pre character for dollar char fragment.",
)))); )));
} }
} }
} }

View File

@@ -27,7 +27,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -81,22 +80,28 @@ where
let object_matcher = parser_with_context!(standard_set_object)(&parser_context); let 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);
// Check for a completely empty block // Check for a completely empty block
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) { let (remaining, contents, children) =
match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => ( Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining, remaining,
whitespace,
if whitespace.len() > 0 {
vec![Object::PlainText(PlainText { vec![Object::PlainText(PlainText {
source: whitespace.into(), source: whitespace.into(),
})], })]
} else {
Vec::new()
},
), ),
Err(_) => { Err(_) => {
let (remaining, (children, _exit_contents)) = let (remaining, (contents, (children, _exit_contents))) =
many_till(object_matcher, exit_matcher)(remaining)?; consumed(many_till(object_matcher, exit_matcher))(remaining)?;
(remaining, children) (remaining, contents, children)
} }
}; };
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -109,6 +114,8 @@ where
), ),
data: parameters.map(Into::<&str>::into), data: parameters.map(Into::<&str>::into),
children, children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -145,7 +152,7 @@ where
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?; let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -157,6 +164,7 @@ where
affiliated_keywords, affiliated_keywords,
), ),
contents: contents.into(), contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -203,10 +211,10 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]); let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?; let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = { let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
@@ -237,7 +245,8 @@ where
retain_labels, retain_labels,
use_labels, use_labels,
label_format, label_format,
contents, value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -277,10 +286,10 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]); let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?; let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -293,7 +302,8 @@ where
), ),
export_type: export_type.map(Into::<&str>::into), export_type: export_type.map(Into::<&str>::into),
data: parameters.map(Into::<&str>::into), data: parameters.map(Into::<&str>::into),
contents, value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -332,10 +342,10 @@ where
let parser_context = context.with_additional_node(&contexts[0]); 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[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?; let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = { let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
@@ -372,7 +382,8 @@ where
retain_labels, retain_labels,
use_labels, use_labels,
label_format, label_format,
contents, value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -604,7 +615,7 @@ fn _example_src_switches<'s>(
} }
} }
if !matched_a_word { if !matched_a_word {
return Err(nom::Err::Error(CustomError::MyError(MyError("No words.")))); return Err(nom::Err::Error(CustomError::Static("No words.")));
} }
let remaining = last_match_remaining; let remaining = last_match_remaining;
@@ -651,51 +662,3 @@ fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n"), is_not(" \t\r\n"),
))(input) ))(input)
} }
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn content<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, String> {
let mut ret = String::new();
let mut remaining = input;
let exit_matcher_parser = parser_with_context!(exit_matcher_parser)(context);
loop {
if exit_matcher_parser(remaining).is_ok() {
break;
}
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
if let Some(val) = pre_escape_whitespace {
ret.push_str(Into::<&str>::into(val));
}
ret.push_str(line.into());
remaining = remain;
}
Ok((remaining, ret))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn content_line<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (Option<OrgSource<'s>>, OrgSource<'s>)> {
let (remaining, pre_escape_whitespace) = opt(map(
tuple((
recognize(tuple((
space0,
many_till(
tag(","),
peek(tuple((tag(","), alt((tag("#+"), tag("*")))))),
),
))),
tag(","),
)),
|(pre_comma, _)| pre_comma,
))(input)?;
let (remaining, line_post_escape) = recognize(many_till(anychar, line_ending))(remaining)?;
Ok((remaining, (pre_escape_whitespace, line_post_escape)))
}

View File

@@ -7,7 +7,6 @@ use nom::multi::many0;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::types::LineBreak; use crate::types::LineBreak;
@@ -45,9 +44,9 @@ fn pre<'b, 'g, 'r, 's>(
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
None | Some('\\') => { None | Some('\\') => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for line break.", "Not a valid pre character for line break.",
)))); )));
} }
_ => {} _ => {}
}; };
@@ -55,9 +54,9 @@ fn pre<'b, 'g, 'r, 's>(
let current_line = input.text_since_line_break(); let current_line = input.text_since_line_break();
let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace()); let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
if !is_non_empty_line { if !is_non_empty_line {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre line for line break.", "Not a valid pre line for line break.",
)))); )));
} }
Ok((input, ())) Ok((input, ()))

View File

@@ -1,6 +1,7 @@
mod affiliated_keyword; mod affiliated_keyword;
mod angle_link; mod angle_link;
mod babel_call; mod babel_call;
mod bullshitium;
mod citation; mod citation;
mod citation_reference; mod citation_reference;
mod clock; mod clock;

View File

@@ -4,7 +4,6 @@ use super::regular_link::regular_link;
use super::subscript_and_superscript::detect_subscript_or_superscript; use super::subscript_and_superscript::detect_subscript_or_superscript;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; 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;
@@ -43,7 +42,7 @@ pub(crate) fn standard_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -61,7 +60,7 @@ pub(crate) fn minimal_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -103,7 +102,7 @@ fn standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(angle_link, context, input, Object::AngleLink); element!(angle_link, context, input, Object::AngleLink);
element!(org_macro, context, input, Object::OrgMacro); element!(org_macro, context, input, Object::OrgMacro);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -119,7 +118,7 @@ fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(entity, context, input, Object::Entity); element!(entity, context, input, Object::Entity);
element!(latex_fragment, context, input, Object::LatexFragment); element!(latex_fragment, context, input, Object::LatexFragment);
element!(text_markup, context, input); element!(text_markup, context, input);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -137,9 +136,7 @@ pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No object detected.")));
"No object detected.",
))));
} }
#[cfg_attr( #[cfg_attr(
@@ -157,9 +154,7 @@ fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No object detected.")));
"No object detected.",
))));
} }
#[cfg_attr( #[cfg_attr(
@@ -182,7 +177,7 @@ pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -205,7 +200,7 @@ fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(inline_babel_call, context, input, Object::InlineBabelCall); element!(inline_babel_call, context, input, Object::InlineBabelCall);
element!(org_macro, context, input, Object::OrgMacro); element!(org_macro, context, input, Object::OrgMacro);
element!(minimal_set_object_sans_plain_text, context, input); element!(minimal_set_object_sans_plain_text, context, input);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -223,9 +218,7 @@ fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No object detected.")))
"No object detected.",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -243,7 +236,7 @@ pub(crate) fn table_cell_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -271,7 +264,7 @@ fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(target, context, input, Object::Target); element!(target, context, input, Object::Target);
element!(timestamp, context, input, Object::Timestamp); element!(timestamp, context, input, Object::Timestamp);
element!(minimal_set_object_sans_plain_text, context, input); element!(minimal_set_object_sans_plain_text, context, input);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -289,7 +282,5 @@ fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No object detected.")));
"No object detected.",
))));
} }

View File

@@ -32,7 +32,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
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 macro_value = get_consumed(input, remaining); let macro_value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -47,6 +47,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
.map(|arg| arg.into()) .map(|arg| arg.into())
.collect(), .collect(),
value: Into::<&str>::into(macro_value), value: Into::<&str>::into(macro_value),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -96,7 +97,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
loop { loop {
not(parser_with_context!(exit_matcher_parser)(context))(remaining)?; not(parser_with_context!(exit_matcher_parser)(context))(remaining)?;
not(peek(tag("}}}")))(remaining)?; not(peek(tag("}}}")))(remaining)?;
if peek(tuple((space0::<OrgSource<'_>, CustomError<_>>, tag(")"))))(remaining).is_ok() { if peek(tuple((space0::<_, CustomError>, tag(")"))))(remaining).is_ok() {
break; break;
} }
@@ -108,7 +109,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
} }
if next_char == '\\' { if next_char == '\\' {
escaping = true; escaping = true;
if peek(tag::<_, _, CustomError<_>>(")"))(new_remaining).is_ok() { if peek(tag::<_, _, CustomError>(")"))(new_remaining).is_ok() {
// Special case for backslash at the end of a macro // Special case for backslash at the end of a macro
remaining = new_remaining; remaining = new_remaining;
break; break;

View File

@@ -10,12 +10,9 @@ use nom::InputTakeAtPosition;
use nom::Offset; use nom::Offset;
use nom::Slice; use nom::Slice;
use crate::error::CustomError;
use crate::error::MyError;
pub(crate) type BracketDepth = i16; pub(crate) type BracketDepth = i16;
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone)]
pub(crate) struct OrgSource<'s> { pub(crate) struct OrgSource<'s> {
full_source: &'s str, full_source: &'s str,
start: usize, start: usize,
@@ -85,6 +82,15 @@ impl<'s> OrgSource<'s> {
self.slice(..(other.end - self.start)) self.slice(..(other.end - self.start))
} }
pub(crate) fn get_until_end_of_str(&self, other: &'s str) -> OrgSource<'s> {
let full_source_start = self.full_source.as_ptr() as usize;
let other_start = other.as_ptr() as usize - full_source_start;
let other_end = other_start + other.len();
debug_assert!(other_start >= self.start);
debug_assert!(other_end <= self.end);
self.slice(..(other_end - self.start))
}
pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> { pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> {
let skipped_text = self.text_since_line_break(); let skipped_text = self.text_since_line_break();
let mut bracket_depth = self.bracket_depth; let mut bracket_depth = self.bracket_depth;
@@ -385,33 +391,6 @@ impl<'n, 's> FindSubstring<&'n str> for OrgSource<'s> {
} }
} }
pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
err: nom::Err<I>,
) -> nom::Err<CustomError<&'a str>> {
match err {
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
nom::Err::Error(err) => nom::Err::Error(err.into()),
nom::Err::Failure(err) => nom::Err::Failure(err.into()),
}
}
impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
fn from(value: CustomError<OrgSource<'s>>) -> Self {
match value {
CustomError::MyError(err) => CustomError::MyError(err),
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
CustomError::IO(err) => CustomError::IO(err),
CustomError::BoxedError(err) => CustomError::BoxedError(err),
}
}
}
impl<'s> From<MyError<OrgSource<'s>>> for MyError<&'s str> {
fn from(value: MyError<OrgSource<'s>>) -> Self {
MyError(value.0.into())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -1,5 +1,8 @@
use nom::branch::alt; use nom::branch::alt;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many1; use nom::multi::many1;
@@ -12,6 +15,7 @@ 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::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass; use crate::context::ExitClass;
@@ -45,14 +49,14 @@ where
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);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(standard_set_object_matcher, exit_matcher), many_till(standard_set_object_matcher, exit_matcher),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
)(remaining)?; ))(remaining)?;
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph. // Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -60,6 +64,8 @@ where
remaining, remaining,
Paragraph { Paragraph {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
affiliated_keywords: parse_affiliated_keywords( affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
@@ -69,6 +75,57 @@ where
)) ))
} }
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn empty_paragraph<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
// If it is just a single newline then source, contents, and post-blank are "\n".
// If it has multiple newlines then contents is the first "\n" and post-blank is all the new lines.
// If there are any spaces on the first line then post-blank excludes the first line.
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line_with_spaces) =
opt(recognize(tuple((space1, org_line_ending))))(input)?;
let post_blank_begin = remaining;
if let Some(first_line_with_spaces) = first_line_with_spaces {
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line_with_spaces),
Some(Into::<&str>::into(first_line_with_spaces)),
Some(Into::<&str>::into(post_blank)),
),
))
} else {
let (remaining, first_line) = blank_line(remaining)?;
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line),
Some(Into::<&str>::into(first_line)),
Some(Into::<&str>::into(post_blank)),
),
))
}
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context))
@@ -96,7 +153,8 @@ mod tests {
use crate::context::List; 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::types::GetStandardProperties; use crate::parser::paragraph::empty_paragraph;
use crate::types::StandardProperties;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
@@ -109,13 +167,20 @@ mod tests {
let (remaining, second_paragraph) = let (remaining, second_paragraph) =
paragraph_matcher(remaining).expect("Parse second paragraph."); paragraph_matcher(remaining).expect("Parse second paragraph.");
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
first_paragraph.get_standard_properties().get_source(), assert_eq!(second_paragraph.get_source(), "lorem ipsum");
"foo bar baz\n\n" }
);
assert_eq!( #[test]
second_paragraph.get_standard_properties().get_source(), fn paragraph_whitespace() {
"lorem ipsum" let input = OrgSource::new("\n");
); 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 = bind_context!(empty_paragraph, &initial_context);
let (remaining, paragraph) = paragraph_matcher(input).expect("Parse paragraph");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(paragraph.get_source(), "\n");
assert_eq!(paragraph.get_contents(), Some("\n"));
} }
} }

View File

@@ -36,7 +36,6 @@ use crate::context::ExitMatcherNode;
use crate::context::Matcher; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
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;
@@ -55,7 +54,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, path_plain) = parse_path_plain(context, remaining)?; let (remaining, path_plain) = parse_path_plain(context, remaining)?;
peek(parser_with_context!(post)(context))(remaining)?; peek(parser_with_context!(post)(context))(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -67,6 +66,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
raw_link: path_plain.raw_link, raw_link: path_plain.raw_link,
search_option: path_plain.search_option, search_option: path_plain.search_option,
application: path_plain.application, application: path_plain.application,
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -95,9 +95,9 @@ fn pre<'b, 'g, 'r, 's>(
Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {} Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {}
Some(_) => { Some(_) => {
// Not at start of line, cannot be a heading // Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for plain link.", "Not a valid pre character for plain link.",
)))); )));
} }
}; };
Ok((input, ())) Ok((input, ()))
@@ -262,15 +262,13 @@ pub(crate) fn protocol<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
for link_parameter in context.get_global_settings().link_parameters { for link_parameter in context.get_global_settings().link_parameters {
let result = tag_no_case::<_, _, CustomError<_>>(*link_parameter)(input); let result = tag_no_case::<_, _, CustomError>(*link_parameter)(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
return Ok((remaining, ent)); return Ok((remaining, ent));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoLinkProtocol")))
"NoLinkProtocol",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -327,8 +325,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
} }
if current_depth == 0 { if current_depth == 0 {
let close_parenthesis = let close_parenthesis = tag::<_, _, CustomError>(")")(remaining);
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
if close_parenthesis.is_ok() { if close_parenthesis.is_ok() {
return close_parenthesis; return close_parenthesis;
} }
@@ -342,9 +339,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No path plain end")))
"No path plain end",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -420,18 +415,18 @@ fn _path_plain_parenthesis_end<'s>(
unreachable!("Exceeded plain link parenthesis depth.") unreachable!("Exceeded plain link parenthesis depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input); let close_parenthesis = tag::<_, _, CustomError>(")")(input);
if close_parenthesis.is_ok() { if close_parenthesis.is_ok() {
return close_parenthesis; return close_parenthesis;
} }
} }
if current_depth == 1 { if current_depth == 1 {
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input); let open_parenthesis = tag::<_, _, CustomError>("(")(input);
if open_parenthesis.is_ok() { if open_parenthesis.is_ok() {
return open_parenthesis; return open_parenthesis;
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static(
"No closing parenthesis", "No closing parenthesis",
)))) )))
} }

View File

@@ -3,9 +3,11 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::multispace1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
@@ -17,6 +19,7 @@ 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 nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::element_parser::element; use super::element_parser::element;
@@ -25,6 +28,7 @@ use super::org_source::OrgSource;
use super::util::include_input; use super::util::include_input;
use super::util::indentation_level; use super::util::indentation_level;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
use crate::context::bind_context;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ContextMatcher; use crate::context::ContextMatcher;
@@ -32,7 +36,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
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;
@@ -78,9 +81,47 @@ where
{ {
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No element detected.")));
"No element detected.", }
))));
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn detect_not_plain_list_item_indent<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (u16, OrgSource<'s>)> {
if let Ok((_remaining, (_, indent, _))) = tuple((
start_of_line,
parser_with_context!(indentation_level)(context),
not(tuple((
parser_with_context!(bullet)(context),
alt((space1, line_ending, eof)),
))),
))(input)
{
return Ok((input, indent));
}
// Headlines are not plain list items.
if let Ok((_remaining, (_, indent, _))) = verify(
tuple((
start_of_line,
parser_with_context!(indentation_level)(context),
tuple((
parser_with_context!(bullet)(context),
alt((space1, line_ending, eof)),
)),
)),
|(_, (depth, _), ((_, bullet), _))| {
*depth == 0 && Into::<&str>::into(bullet).starts_with('*')
},
)(input)
{
return Ok((input, indent));
}
return Err(nom::Err::Error(CustomError::Static("No element detected.")));
} }
#[cfg_attr( #[cfg_attr(
@@ -112,6 +153,7 @@ where
let mut children = Vec::new(); let mut children = Vec::new();
let mut first_item_indentation: Option<IndentationLevel> = None; let mut first_item_indentation: Option<IndentationLevel> = None;
let mut first_item_list_type: Option<PlainListType> = None; let mut first_item_list_type: Option<PlainListType> = None;
let contents_begin = remaining;
let mut remaining = remaining; let mut remaining = remaining;
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here: // The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
@@ -123,7 +165,7 @@ where
// While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed. // While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed.
loop { loop {
let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining); let list_item = plain_list_item(&parser_context, remaining);
match (&first_item_list_type, &list_item) { match (&first_item_list_type, &list_item) {
(None, Ok((_remain, (list_type, _item)))) => { (None, Ok((_remain, (list_type, _item)))) => {
let _ = first_item_list_type.insert(*list_type); let _ = first_item_list_type.insert(*list_type);
@@ -143,27 +185,20 @@ where
} }
}; };
let maybe_exit = parser_with_context!(exit_matcher_parser)(&parser_context)(remaining); let maybe_exit = exit_matcher_parser(&parser_context, remaining);
if maybe_exit.is_ok() { if maybe_exit.is_ok() {
break; break;
} }
} }
let (final_child_start, _final_item_first_parse) = match children.pop() { if children.is_empty() {
Some(final_child) => final_child, return Err(nom::Err::Error(CustomError::Static(
None => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Plain lists require at least one element.", "Plain lists require at least one element.",
)))); )));
} }
};
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (remaining, (_, reparsed_final_item)) =
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
children.push((final_child_start, reparsed_final_item));
let (remaining, _trailing_ws) = let contents = get_consumed(contents_begin, remaining);
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -176,6 +211,8 @@ where
), ),
list_type: first_item_list_type.expect("Plain lists require at least one element."), list_type: first_item_list_type.expect("Plain lists require at least one element."),
children: children.into_iter().map(|(_start, item)| item).collect(), children: children.into_iter().map(|(_start, item)| item).collect(),
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -190,10 +227,10 @@ fn plain_list_item<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> { ) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?; let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
let (remaining, (bullet_type, bull)) = verify( let (remaining, (bullet_type, bull)) =
parser_with_context!(bullet)(context), verify(bind_context!(bullet, context), |(_bullet_type, bull)| {
|(_bullet_type, bull)| !Into::<&str>::into(bull).starts_with('*') || indent_level > 0, !Into::<&str>::into(bull).starts_with('*') || indent_level > 0
)(remaining)?; })(remaining)?;
let (remaining, maybe_counter_set) = let (remaining, maybe_counter_set) =
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?; opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
@@ -202,7 +239,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?; let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type { let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type {
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)? opt(tuple((space1, bind_context!(item_tag, context))))(remaining)?
} else { } else {
(remaining, None) (remaining, None)
}; };
@@ -214,6 +251,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
}; };
let exit_matcher = plain_list_item_end(indent_level); let exit_matcher = plain_list_item_end(indent_level);
let final_item_whitespace_cutoff = final_item_whitespace_cutoff(indent_level);
let final_whitespace_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &final_item_whitespace_cutoff,
});
let final_whitespace_context = context.with_additional_node(&final_whitespace_context);
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -221,17 +264,21 @@ fn plain_list_item<'b, 'g, 'r, 's>(
exit_matcher: &exit_matcher, exit_matcher: &exit_matcher,
}), }),
]; ];
let parser_context = context.with_additional_node(&contexts[0]); let parser_context = final_whitespace_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[1]);
let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!( let maybe_contentless_item: Res<OrgSource<'_>, ()> =
detect_contentless_item_contents detect_contentless_item_contents(&parser_context, remaining);
)(&parser_context))(remaining);
if let Ok((_rem, _ws)) = maybe_contentless_item { if let Ok((_rem, _ws)) = maybe_contentless_item {
let (remaining, _trailing_ws) = if context.should_consume_trailing_whitespace() { let (remaining, post_blank) = if tuple((
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)? blank_line,
} else { bind_context!(final_item_whitespace_cutoff, context),
))(remaining)
.is_ok()
{
recognize(alt((blank_line, eof)))(remaining)? recognize(alt((blank_line, eof)))(remaining)?
} else {
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
}; };
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
return Ok(( return Ok((
@@ -249,6 +296,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
pre_blank: 0, pre_blank: 0,
children: Vec::new(), children: Vec::new(),
contents: None,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
}, },
), ),
)); ));
@@ -259,26 +312,14 @@ fn plain_list_item<'b, 'g, 'r, 's>(
.filter(|b| *b == b'\n') .filter(|b| *b == b'\n')
.count(); .count();
let (mut remaining, (mut children, _exit_contents)) = many_till( let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
include_input(parser_with_context!(element(true))(&parser_context)), include_input(bind_context!(element(true), &parser_context)),
parser_with_context!(exit_matcher_parser)(&parser_context), bind_context!(exit_matcher_parser, &parser_context),
)(remaining)?; ))(remaining)?;
if !children.is_empty() && !context.should_consume_trailing_whitespace() { // We have to use the parser_context here to include the whitespace cut-off
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false); let (remaining, post_blank) =
let final_item_context = parser_context.with_additional_node(&final_item_context); maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
let (final_child_start, _original_final_child) = children
.pop()
.expect("if-statement already checked that children was non-empty.");
let (remain, reparsed_final_element) = include_input(parser_with_context!(element(true))(
&final_item_context,
))(final_child_start)?;
remaining = remain;
children.push(reparsed_final_element);
}
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
return Ok(( return Ok((
@@ -299,6 +340,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
pre_blank: PlainListItemPreBlank::try_from(pre_blank) pre_blank: PlainListItemPreBlank::try_from(pre_blank)
.expect("pre-blank cannot be larger than 2."), .expect("pre-blank cannot be larger than 2."),
children: children.into_iter().map(|(_start, item)| item).collect(), children: children.into_iter().map(|(_start, item)| item).collect(),
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
}, },
), ),
)); ));
@@ -325,7 +372,7 @@ fn bullet<'b, 'g, 'r, 's>(
map(tag("+"), |bull| (BulletType::Unordered, bull)), map(tag("+"), |bull| (BulletType::Unordered, bull)),
map( map(
recognize(tuple(( recognize(tuple((
parser_with_context!(counter)(context), bind_context!(counter, context),
alt((tag("."), tag(")"))), alt((tag("."), tag(")"))),
))), ))),
|bull| (BulletType::Ordered, bull), |bull| (BulletType::Ordered, bull),
@@ -380,6 +427,52 @@ fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PlainListIt
))(input) ))(input)
} }
const fn final_item_whitespace_cutoff(indent_level: IndentationLevel) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| {
impl_final_item_whitespace_cutoff(context, input, indent_level)
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn impl_final_item_whitespace_cutoff<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
indent_level: IndentationLevel,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
// element!(plain_list_end, context, input);
if let Ok((_remaining, _)) = verify(
tuple((
opt(blank_line),
bind_context!(indentation_level, context),
not(multispace1),
)),
|(_, (depth, _stars), _not_whitespace)| *depth < indent_level,
)(input)
{
return Ok((input, input.take(0)));
}
if let Ok((_remaining, _)) = tuple((
opt(blank_line),
verify(
bind_context!(detect_not_plain_list_item_indent, context),
|(depth, _)| *depth == indent_level,
),
))(input)
{
return Ok((input, input.take(0)));
}
Err(nom::Err::Error(CustomError::Static(
"No whitespace cut-off.",
)))
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context)) tracing::instrument(ret, level = "debug", skip(_context))
@@ -415,7 +508,7 @@ fn _plain_list_item_end<'b, 'g, 'r, 's>(
start_of_line(input)?; start_of_line(input)?;
recognize(tuple(( recognize(tuple((
opt(blank_line), opt(blank_line),
parser_with_context!(line_indented_lte_matcher)(context), bind_context!(line_indented_lte_matcher, context),
)))(input) )))(input)
} }
@@ -434,7 +527,7 @@ fn _line_indented_lte<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let matched = recognize(verify( let matched = recognize(verify(
tuple(( tuple((
parser_with_context!(indentation_level)(context), bind_context!(indentation_level, context),
non_whitespace_character, non_whitespace_character,
)), )),
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09) // It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
@@ -460,8 +553,8 @@ fn item_tag<'b, 'g, 'r, 's>(
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
// TODO: Should this be using a different set like the minimal set? // TODO: Should this be using a different set like the minimal set?
parser_with_context!(standard_set_object)(&parser_context), bind_context!(standard_set_object, &parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context), bind_context!(exit_matcher_parser, &parser_context),
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
)(input)?; )(input)?;
@@ -511,7 +604,7 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
alt(( alt((
peek(recognize(not(blank_line))), peek(recognize(not(blank_line))),
peek(recognize(tuple((many0(blank_line), eof)))), peek(recognize(tuple((many0(blank_line), eof)))),
parser_with_context!(exit_matcher_parser)(context), bind_context!(exit_matcher_parser, context),
)), )),
), ),
))), ))),
@@ -541,7 +634,7 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = recognize(many_till( let (remaining, _) = recognize(many_till(
blank_line, blank_line,
parser_with_context!(exit_matcher_parser)(context), bind_context!(exit_matcher_parser, context),
))(input)?; ))(input)?;
Ok((remaining, ())) Ok((remaining, ()))
} }
@@ -553,7 +646,7 @@ mod tests {
use crate::context::Context; use crate::context::Context;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn plain_list_item_empty() { fn plain_list_item_empty() {
@@ -564,7 +657,7 @@ mod tests {
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context); let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap(); let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1."); assert_eq!(result.get_source(), "1.");
} }
#[test] #[test]
@@ -576,7 +669,7 @@ mod tests {
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context); let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap(); let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo"); assert_eq!(result.get_source(), "1. foo");
} }
#[test] #[test]
@@ -588,7 +681,7 @@ mod tests {
let (remaining, result) = let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap(); plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1."); assert_eq!(result.get_source(), "1.");
} }
#[test] #[test]
@@ -600,7 +693,7 @@ mod tests {
let (remaining, result) = let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap(); plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo"); assert_eq!(result.get_source(), "1. foo");
} }
#[test] #[test]
@@ -645,7 +738,7 @@ mod tests {
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), " ipsum\n"); assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
assert_eq!( assert_eq!(
result.get_standard_properties().get_source(), result.get_source(),
r#"1. foo r#"1. foo
2. bar 2. bar
baz baz
@@ -673,7 +766,7 @@ baz"#,
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "baz"); assert_eq!(Into::<&str>::into(remaining), "baz");
assert_eq!( assert_eq!(
result.get_standard_properties().get_source(), result.get_source(),
r#"1. foo r#"1. foo
1. bar 1. bar
@@ -706,7 +799,7 @@ dolar"#,
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "dolar"); assert_eq!(Into::<&str>::into(remaining), "dolar");
assert_eq!( assert_eq!(
result.get_standard_properties().get_source(), result.get_source(),
r#"1. foo r#"1. foo
bar bar

View File

@@ -20,7 +20,6 @@ use super::util::org_space_or_line_ending;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainText; use crate::types::PlainText;
@@ -92,7 +91,7 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
break; break;
} }
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal); let is_not_whitespace = is_not::<_, _, CustomError>(" \t\r\n")(goal);
if let Ok((new_goal, payload)) = is_not_whitespace { if let Ok((new_goal, payload)) = is_not_whitespace {
let (new_remaining, _) = tuple(( let (new_remaining, _) = tuple((
tag_no_case(payload), tag_no_case(payload),
@@ -108,7 +107,7 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
} }
let is_whitespace = recognize(many1(alt(( let is_whitespace = recognize(many1(alt((
recognize(one_of::<&str, &str, CustomError<_>>(" \t")), recognize(one_of::<_, _, CustomError>(" \t")),
line_ending, line_ending,
))))(goal); ))))(goal);
if let Ok((new_goal, _)) = is_whitespace { if let Ok((new_goal, _)) = is_whitespace {
@@ -118,9 +117,9 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
continue; continue;
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Target does not match.", "Target does not match.",
)))); )));
} }
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -144,7 +143,7 @@ mod tests {
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text; use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn plain_text_simple() { fn plain_text_simple() {
@@ -161,9 +160,6 @@ mod tests {
)(input) )(input)
.unwrap(); .unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(result.get_source(), Into::<&str>::into(input));
result.get_standard_properties().get_source(),
Into::<&str>::into(input)
);
} }
} }

View File

@@ -33,7 +33,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
many1(parser_with_context!(planning_parameter)(context))(remaining)?; many1(parser_with_context!(planning_parameter)(context))(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?; let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -62,6 +62,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
scheduled, scheduled,
deadline, deadline,
closed, closed,
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -6,6 +6,7 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
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::recognize;
@@ -21,7 +22,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::util::immediate_in_section; use crate::parser::util::immediate_in_section;
@@ -39,9 +39,9 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PropertyDrawer<'s>> { ) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
if immediate_in_section(context, "property-drawer") { if immediate_in_section(context, "property-drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
let ( let (
remaining, remaining,
@@ -65,14 +65,11 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]); 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[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, (contents, children)) =
let node_property_matcher = parser_with_context!(node_property)(&parser_context); consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(remaining)?;
let (remaining, _end) = property_drawer_end(&parser_context, remaining)?; let (remaining, _end) = property_drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -81,10 +78,31 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
PropertyDrawer { PropertyDrawer {
source: source.into(), source: source.into(),
children, children,
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<NodeProperty<'s>>> {
let node_property_matcher = parser_with_context!(node_property)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context)) tracing::instrument(ret, level = "debug", skip(_context))

View File

@@ -21,7 +21,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::types::Object; use crate::types::Object;
@@ -40,7 +39,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
let rematched_target = rematch_target(context, radio_target, input); let rematched_target = rematch_target(context, radio_target, input);
if let Ok((remaining, rematched_target)) = rematched_target { if let Ok((remaining, rematched_target)) = rematched_target {
let path = get_consumed(input, remaining); let path = get_consumed(input, remaining);
let (remaining, _) = space0(remaining)?; let (remaining, post_blank) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
return Ok(( return Ok((
remaining, remaining,
@@ -48,13 +47,16 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
source: source.into(), source: source.into(),
children: rematched_target, children: rematched_target,
path: path.into(), path: path.into(),
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
}, },
)); ));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoRadioLink")))
"NoRadioLink",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -98,9 +100,9 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
new_matches.push(new_match); new_matches.push(new_match);
} }
_ => { _ => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"OnlyMinimalSetObjectsAllowed", "OnlyMinimalSetObjectsAllowed",
)))); )));
} }
}; };
} }
@@ -137,7 +139,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
))(remaining)?; ))(remaining)?;
let (remaining, _closing) = tag(">>>")(remaining)?; let (remaining, _closing) = tag(">>>")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -145,6 +147,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
RadioTarget { RadioTarget {
source: source.into(), source: source.into(),
value: raw_value.into(), value: raw_value.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -178,11 +181,11 @@ mod tests {
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::types::Bold; use crate::types::Bold;
use crate::types::Element; use crate::types::Element;
use crate::types::GetStandardProperties;
use crate::types::PlainText; use crate::types::PlainText;
use crate::types::StandardProperties;
#[test] #[test]
fn plain_text_radio_target() { fn plain_text_radio_target() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("foo bar baz"); let input = OrgSource::new("foo bar baz");
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })]; let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
let global_settings = GlobalSettings { let global_settings = GlobalSettings {
@@ -198,29 +201,38 @@ mod tests {
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "foo bar baz");
first_paragraph.get_standard_properties().get_source(),
"foo bar baz"
);
assert_eq!(first_paragraph.children.len(), 3); assert_eq!(first_paragraph.children.len(), 3);
assert_eq!( match first_paragraph
first_paragraph
.children .children
.get(1) .get(1)
.expect("Len already asserted to be 3"), .expect("Len already asserted to be 3.")
&Object::RadioLink(RadioLink { {
source: "bar ", Object::RadioLink(inner) => {
children: vec![Object::PlainText(PlainText { source: "bar" })], assert_eq!(inner.get_source(), "bar ");
path: "bar" assert_eq!(inner.path, "bar");
}) assert_eq!(inner.children.len(), 1);
); let child = inner
.children
.first()
.expect("Length already asserted to be 1.");
assert!(matches!(child, Object::PlainText(_)));
assert_eq!(child.get_source(), "bar");
}
_ => {
return Err("Child should be a radio link.".into());
}
};
Ok(())
} }
#[test] #[test]
fn bold_radio_target() { fn bold_radio_target() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("foo *bar* baz"); let input = OrgSource::new("foo *bar* baz");
let radio_target_match = vec![Object::Bold(Bold { let radio_target_match = vec![Object::Bold(Bold {
source: "*bar*", source: "*bar*",
contents: "bar",
post_blank: Some(" "),
children: vec![Object::PlainText(PlainText { source: "bar" })], children: vec![Object::PlainText(PlainText { source: "bar" })],
})]; })];
let global_settings = GlobalSettings { let global_settings = GlobalSettings {
@@ -237,24 +249,28 @@ mod tests {
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
first_paragraph.get_standard_properties().get_source(),
"foo *bar* baz"
);
assert_eq!(first_paragraph.children.len(), 3); assert_eq!(first_paragraph.children.len(), 3);
assert_eq!( match first_paragraph
first_paragraph
.children .children
.get(1) .get(1)
.expect("Len already asserted to be 3"), .expect("Len already asserted to be 3.")
&Object::RadioLink(RadioLink { {
source: "*bar* ", Object::RadioLink(inner) => {
children: vec![Object::Bold(Bold { assert_eq!(inner.get_source(), "*bar* ");
source: "*bar* ", assert_eq!(inner.path, "*bar* ");
children: vec![Object::PlainText(PlainText { source: "bar" })] assert_eq!(inner.children.len(), 1);
})], let child = inner
path: "*bar* " .children
}) .first()
); .expect("Length already asserted to be 1.");
assert!(matches!(child, Object::Bold(_)));
assert_eq!(child.get_source(), "*bar* ");
}
_ => {
return Err("Child should be a radio link.".into());
}
};
Ok(())
} }
} }

View File

@@ -43,7 +43,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::LinkType; use crate::types::LinkType;
use crate::types::Object; use crate::types::Object;
@@ -74,7 +73,7 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, path) = pathreg(context, remaining)?; let (remaining, path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -85,6 +84,8 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
path: path.path, path: path.path,
raw_link: path.raw_link, raw_link: path.raw_link,
search_option: path.search_option, search_option: path.search_option,
contents: None,
post_blank: post_blank.map(Into::<&str>::into),
children: Vec::new(), children: Vec::new(),
application: path.application, application: path.application,
}, },
@@ -102,9 +103,10 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, path) = pathreg(context, remaining)?; let (remaining, path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("][")(remaining)?; let (remaining, _closing_bracket) = tag("][")(remaining)?;
let (remaining, description) = description(context, remaining)?; let (remaining, (contents, description)) =
consumed(parser_with_context!(description)(context))(remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -115,6 +117,8 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
path: path.path, path: path.path,
raw_link: path.raw_link, raw_link: path.raw_link,
search_option: path.search_option, search_option: path.search_option,
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
children: description, children: description,
application: path.application, application: path.application,
}, },
@@ -163,11 +167,7 @@ fn parse_path_reg<'b, 'g, 'r, 's>(
parser_with_context!(protocol_path_reg)(context), parser_with_context!(protocol_path_reg)(context),
fuzzy_path_reg, fuzzy_path_reg,
))(replaced_input) ))(replaced_input)
.map_err(|_| { .map_err(|_| nom::Err::Error(CustomError::Static("No pathreg match after replacement.")))?;
nom::Err::Error(CustomError::MyError(MyError(
"No pathreg match after replacement.",
)))
})?;
let remaining = input.take(input.len()); let remaining = input.take(input.len());
let link_type = match link.link_type { let link_type = match link.link_type {
LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()), LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()),
@@ -483,7 +483,5 @@ fn impl_path_reg_end<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No path reg end")))
"No path reg end",
))))
} }

View File

@@ -72,7 +72,7 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
} }
} }
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -80,6 +80,7 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
remaining, remaining,
Section { Section {
source: source.into(), source: source.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -128,7 +129,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
children.insert(0, ele) children.insert(0, ele)
} }
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -136,6 +137,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
remaining, remaining,
Section { Section {
source: source.into(), source: source.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))

View File

@@ -40,7 +40,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
tag("%]"), tag("%]"),
)))(input)?; )))(input)?;
let value = get_consumed(input, remaining); let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -48,6 +48,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie { StatisticsCookie {
source: source.into(), source: source.into(),
value: value.into(), value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -68,7 +69,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
tag("]"), tag("]"),
)))(input)?; )))(input)?;
let value = get_consumed(input, remaining); let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -76,6 +77,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie { StatisticsCookie {
source: source.into(), source: source.into(),
value: value.into(), value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::take_while; use nom::bytes::complete::take_while;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::combinator::consumed;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
@@ -26,7 +27,6 @@ use crate::context::ExitMatcherNode;
use crate::context::Matcher; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::types::Object; use crate::types::Object;
@@ -39,7 +39,7 @@ pub(crate) fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<O
// This does not have to detect all valid subscript/superscript but all that it detects must be valid. // This does not have to detect all valid subscript/superscript but all that it detects must be valid.
let (remaining, _) = one_of("_^")(input)?; let (remaining, _) = one_of("_^")(input)?;
pre(input)?; pre(input)?;
if tag::<_, _, CustomError<_>>("*")(remaining).is_ok() { if tag::<_, _, CustomError>("*")(remaining).is_ok() {
return Ok((input, ())); return Ok((input, ()));
} }
let (remaining, _) = opt(one_of("+-"))(remaining)?; let (remaining, _) = opt(one_of("+-"))(remaining)?;
@@ -55,17 +55,20 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Subscript<'s>> { ) -> Res<OrgSource<'s>, Subscript<'s>> {
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("_")(input)?; let (remaining, _) = tag("_")(input)?;
pre(input)?; pre(input)?;
let (remaining, body) = script_body(context, remaining)?; let (remaining, body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let (use_brackets, body) = match body { let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]), ScriptBody::Braceless(text) => (
ScriptBody::WithBraces(body) => (true, body), false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
}; };
Ok(( Ok((
@@ -73,6 +76,8 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
Subscript { Subscript {
source: source.into(), source: source.into(),
use_brackets, use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body, children: body,
}, },
)) ))
@@ -90,13 +95,17 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
let (remaining, _) = tag("^")(input)?; let (remaining, _) = tag("^")(input)?;
pre(input)?; pre(input)?;
let (remaining, body) = script_body(context, remaining)?; let (remaining, body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let (use_brackets, body) = match body { let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]), ScriptBody::Braceless(text) => (
ScriptBody::WithBraces(body) => (true, body), false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
}; };
Ok(( Ok((
@@ -104,6 +113,8 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
Superscript { Superscript {
source: source.into(), source: source.into(),
use_brackets, use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body, children: body,
}, },
)) ))
@@ -118,7 +129,7 @@ fn pre<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
#[derive(Debug)] #[derive(Debug)]
enum ScriptBody<'s> { enum ScriptBody<'s> {
Braceless(&'s str), Braceless(&'s str),
WithBraces(Vec<Object<'s>>), WithBraces(&'s str, Vec<Object<'s>>),
} }
#[cfg_attr( #[cfg_attr(
@@ -136,9 +147,10 @@ fn script_body<'b, 'g, 'r, 's>(
map(parser_with_context!(script_alphanum)(context), |body| { map(parser_with_context!(script_alphanum)(context), |body| {
ScriptBody::Braceless(body.into()) ScriptBody::Braceless(body.into())
}), }),
map(parser_with_context!(script_with_braces)(context), |body| { map(
ScriptBody::WithBraces(body) parser_with_context!(script_with_braces)(context),
}), |(contents, body)| ScriptBody::WithBraces(Into::<&str>::into(contents), body),
),
map( map(
parser_with_context!(script_with_parenthesis)(context), parser_with_context!(script_with_parenthesis)(context),
|body| ScriptBody::Braceless(body.into()), |body| ScriptBody::Braceless(body.into()),
@@ -196,7 +208,7 @@ fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>,
fn script_with_braces<'b, 'g, 'r, 's>( fn script_with_braces<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>)> {
let (remaining, _) = tag("{")(input)?; let (remaining, _) = tag("{")(input)?;
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth()); let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -205,13 +217,13 @@ fn script_with_braces<'b, 'g, 'r, 's>(
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = many_till( let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
)(remaining)?; ))(remaining)?;
let (remaining, _) = tag("}")(remaining)?; let (remaining, _) = tag("}")(remaining)?;
Ok((remaining, children)) Ok((remaining, (contents, children)))
} }
fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher { fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher {
@@ -232,9 +244,9 @@ fn _script_with_braces_end<'b, 'g, 'r, 's>(
let current_depth = input.get_brace_depth() - starting_brace_depth; let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep // Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid end for subscript or superscript.", "Not a valid end for subscript or superscript.",
)))); )));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript. // This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
@@ -282,12 +294,12 @@ fn _script_with_parenthesis_end<'s>(
unreachable!("Exceeded citation key suffix bracket depth.") unreachable!("Exceeded citation key suffix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input); let close_parenthesis = tag::<_, _, CustomError>(")")(input);
if close_parenthesis.is_ok() { if close_parenthesis.is_ok() {
return close_parenthesis; return close_parenthesis;
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static(
"No script parenthesis end.", "No script parenthesis end.",
)))) )))
} }

View File

@@ -3,6 +3,7 @@ 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::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
@@ -67,13 +68,13 @@ where
let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&parser_context); let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&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 (remaining, (contents, (children, _exit_contents))) =
many_till(org_mode_table_row_matcher, exit_matcher)(remaining)?; consumed(many_till(org_mode_table_row_matcher, exit_matcher))(remaining)?;
let (remaining, formulas) = let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?; many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -87,6 +88,8 @@ where
), ),
formulas, formulas,
children, children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -150,6 +153,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
TableRow { TableRow {
source: source.into(), source: source.into(),
children: Vec::new(), children: Vec::new(),
contents: None,
}, },
)) ))
} }
@@ -164,8 +168,8 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _) = tuple((space0, tag("|")))(input)?; let (remaining, _) = tuple((space0, tag("|")))(input)?;
let (remaining, children) = let (remaining, (contents, children)) =
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?; consumed(many1(parser_with_context!(org_mode_table_cell)(context)))(remaining)?;
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?; let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -173,6 +177,11 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
TableRow { TableRow {
source: source.into(), source: source.into(),
children, children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
}, },
)) ))
} }
@@ -194,12 +203,12 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
parser_with_context!(table_cell_set_object)(&parser_context); parser_with_context!(table_cell_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);
let (remaining, _) = space0(input)?; let (remaining, _) = space0(input)?;
let (remaining, (children, _exit_contents)) = verify( let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(table_cell_set_object_matcher, exit_matcher), many_till(table_cell_set_object_matcher, exit_matcher),
|(children, exit_contents)| { |(children, exit_contents)| {
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|') !children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
}, },
)(remaining)?; ))(remaining)?;
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?; let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
@@ -210,6 +219,7 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
TableCell { TableCell {
source: source.into(), source: source.into(),
children, children,
contents: Into::<&str>::into(contents),
}, },
)) ))
} }

View File

@@ -13,7 +13,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
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::types::Target; use crate::types::Target;
@@ -42,12 +41,12 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
.get_preceding_character() .get_preceding_character()
.expect("We cannot be at the start of the file because we are inside a target."); .expect("We cannot be at the start of the file because we are inside a target.");
if preceding_character.is_whitespace() { if preceding_character.is_whitespace() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Targets cannot end with whitespace.", "Targets cannot end with whitespace.",
)))); )));
} }
let (remaining, _) = tag(">>")(remaining)?; let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -56,6 +55,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
Target { Target {
source: source.into(), source: source.into(),
value: body.into(), value: body.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -3,11 +3,13 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::multispace1; use nom::character::complete::multispace1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space1;
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::consumed;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::map_parser; use nom::combinator::map_parser;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -34,7 +36,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::radio_link::rematch_target; use crate::parser::radio_link::rematch_target;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
@@ -77,12 +78,14 @@ fn bold<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Bold<'s>> { ) -> Res<OrgSource<'s>, Bold<'s>> {
let (remaining, children) = text_markup_object("*")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("*")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Bold { Bold {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -96,12 +99,14 @@ fn italic<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Italic<'s>> { ) -> Res<OrgSource<'s>, Italic<'s>> {
let (remaining, children) = text_markup_object("/")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("/")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Italic { Italic {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -115,12 +120,14 @@ fn underline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Underline<'s>> { ) -> Res<OrgSource<'s>, Underline<'s>> {
let (remaining, children) = text_markup_object("_")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("_")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Underline { Underline {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -134,12 +141,14 @@ fn strike_through<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StrikeThrough<'s>> { ) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
let (remaining, children) = text_markup_object("+")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("+")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
StrikeThrough { StrikeThrough {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -153,13 +162,14 @@ fn verbatim<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Verbatim<'s>> { ) -> Res<OrgSource<'s>, Verbatim<'s>> {
let (remaining, contents) = text_markup_string("=")(context, input)?; let (remaining, (contents, post_blank)) = text_markup_string("=")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Verbatim { Verbatim {
source: source.into(), source: source.into(),
contents: contents.into(), contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -172,13 +182,14 @@ fn code<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Code<'s>> { ) -> Res<OrgSource<'s>, Code<'s>> {
let (remaining, contents) = text_markup_string("~")(context, input)?; let (remaining, (contents, post_blank)) = text_markup_string("~")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Code { Code {
source: source.into(), source: source.into(),
contents: contents.into(), contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -188,8 +199,10 @@ fn text_markup_object(
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>, OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> ) -> Res<
+ '_ { OrgSource<'s>,
(OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>),
> + '_ {
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol) move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
} }
@@ -201,7 +214,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = let (remaining, _peek_not_whitespace) =
@@ -216,7 +229,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context)); let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser( let (remaining, (contents, children)) = consumed(map_parser(
verify( verify(
parser_with_context!(text_until_exit)(&parser_context), parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0, |text| text.len() > 0,
@@ -226,7 +239,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
&initial_context, &initial_context,
)))(i) )))(i)
}), }),
)(remaining)?; ))(remaining)?;
{ {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@@ -234,16 +247,16 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.",
)))); )));
} }
} }
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, children)) Ok((remaining, (contents, children, post_blank)))
} }
fn text_markup_string( fn text_markup_string(
@@ -251,7 +264,7 @@ fn text_markup_string(
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>, OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> ) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)>
+ '_ { + '_ {
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol) move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
} }
@@ -264,7 +277,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = let (remaining, _peek_not_whitespace) =
@@ -290,16 +303,16 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.",
)))); )));
} }
} }
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, contents)) Ok((remaining, (contents, post_blank)))
} }
#[cfg_attr( #[cfg_attr(
@@ -321,9 +334,9 @@ fn pre<'b, 'g, 'r, 's>(
// If None, we are at the start of the file which is technically the beginning of a line. // If None, we are at the start of the file which is technically the beginning of a line.
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {} Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
Some(_) => { Some(_) => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for text markup.", "Not a valid pre character for text markup.",
)))); )));
} }
None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above. None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above.
}; };
@@ -360,9 +373,9 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
contents_start_offset: usize, contents_start_offset: usize,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
if input.get_byte_offset() == contents_start_offset { if input.get_byte_offset() == contents_start_offset {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Text markup cannot be empty", "Text markup cannot be empty",
)))); )));
} }
not(preceded_by_whitespace(false))(input)?; not(preceded_by_whitespace(false))(input)?;
let (remaining, _marker) = terminated( let (remaining, _marker) = terminated(
@@ -383,13 +396,15 @@ impl<'x> RematchObject<'x> for Bold<'x> {
_context: RefContext<'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, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "*", &self.children)?; _rematch_text_markup_object(_context, input, "*", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::Bold(Bold { Object::Bold(Bold {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -406,13 +421,15 @@ impl<'x> RematchObject<'x> for Italic<'x> {
_context: RefContext<'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, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "/", &self.children)?; _rematch_text_markup_object(_context, input, "/", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::Italic(Italic { Object::Italic(Italic {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -429,13 +446,15 @@ impl<'x> RematchObject<'x> for Underline<'x> {
_context: RefContext<'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, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "_", &self.children)?; _rematch_text_markup_object(_context, input, "_", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::Underline(Underline { Object::Underline(Underline {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -452,13 +471,15 @@ impl<'x> RematchObject<'x> for StrikeThrough<'x> {
_context: RefContext<'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, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "+", &self.children)?; _rematch_text_markup_object(_context, input, "+", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::StrikeThrough(StrikeThrough { Object::StrikeThrough(StrikeThrough {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -474,7 +495,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'static str, marker_symbol: &'static str,
original_match_children: &'x Vec<Object<'x>>, original_match_children: &'x Vec<Object<'x>>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?; let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
@@ -485,6 +506,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let contents_begin = remaining;
let (remaining, children) = let (remaining, children) =
// TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code. // TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code.
rematch_target(&parser_context, original_match_children, remaining)?; rematch_target(&parser_context, original_match_children, remaining)?;
@@ -495,13 +517,15 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.",
)))); )));
} }
} }
let contents_end = remaining;
let contents = contents_begin.get_until(contents_end);
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, post_blank) = opt(space1)(remaining)?;
Ok((remaining, children)) Ok((remaining, (contents, children, post_blank)))
} }

View File

@@ -53,8 +53,8 @@ pub(crate) fn timestamp<'b, 'g, 'r, 's>(
parser_with_context!(inactive_time_range_timestamp)(context), parser_with_context!(inactive_time_range_timestamp)(context),
parser_with_context!(active_date_range_timestamp)(context), parser_with_context!(active_date_range_timestamp)(context),
parser_with_context!(inactive_date_range_timestamp)(context), parser_with_context!(inactive_date_range_timestamp)(context),
parser_with_context!(active_timestamp)(context), parser_with_context!(active_timestamp(true))(context),
parser_with_context!(inactive_timestamp)(context), parser_with_context!(inactive_timestamp(true))(context),
))(input) ))(input)
} }
@@ -69,7 +69,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
let (remaining, _) = tag("<%%(")(input)?; let (remaining, _) = tag("<%%(")(input)?;
let (remaining, _body) = sexp(context, remaining)?; let (remaining, _body) = sexp(context, remaining)?;
let (remaining, _) = tag(")>")(remaining)?; let (remaining, _) = tag(")>")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -85,6 +85,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
end_time: None, end_time: None,
repeater: None, repeater: None,
warning_delay: None, warning_delay: None,
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -125,13 +126,23 @@ fn sexp_end<'b, 'g, 'r, 's>(
alt((tag(")>"), recognize(one_of(">\n"))))(input) alt((tag(")>"), recognize(one_of(">\n"))))(input)
} }
const fn active_timestamp(
allow_post_blank: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
move |context, input| impl_active_timestamp(context, input, allow_post_blank)
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context))
)] )]
fn active_timestamp<'b, 'g, 'r, 's>( fn impl_active_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
allow_post_blank: bool,
) -> Res<OrgSource<'s>, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?; let (remaining, _) = tag("<")(input)?;
let (remaining, start) = date(context, remaining)?; let (remaining, start) = date(context, remaining)?;
@@ -159,8 +170,11 @@ fn active_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) = if allow_post_blank {
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
} else {
(remaining, None)
};
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -175,17 +189,28 @@ fn active_timestamp<'b, 'g, 'r, 's>(
end_time: time.map(|(_, time)| time), end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater), repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
pub(crate) const fn inactive_timestamp(
allow_post_blank: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
move |context, input| impl_inactive_timestamp(context, input, allow_post_blank)
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context))
)] )]
pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>( fn impl_inactive_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
allow_post_blank: bool,
) -> Res<OrgSource<'s>, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let (remaining, start) = date(context, remaining)?; let (remaining, start) = date(context, remaining)?;
@@ -213,8 +238,11 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) = if allow_post_blank {
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
} else {
(remaining, None)
};
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -229,6 +257,7 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
end_time: time.map(|(_, time)| time), end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater), repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -241,12 +270,12 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, first_timestamp) = active_timestamp(context, input)?; let (remaining, first_timestamp) = impl_active_timestamp(context, input, false)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace // TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?; let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, second_timestamp) = active_timestamp(context, remaining)?; let (remaining, second_timestamp) = impl_active_timestamp(context, remaining, false)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -264,6 +293,7 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
warning_delay: first_timestamp warning_delay: first_timestamp
.warning_delay .warning_delay
.or(second_timestamp.warning_delay), .or(second_timestamp.warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -302,7 +332,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -318,6 +348,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
end_time: Some(second_time), end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater), repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -330,12 +361,12 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, first_timestamp) = inactive_timestamp(context, input)?; let (remaining, first_timestamp) = impl_inactive_timestamp(context, input, false)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace // TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?; let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?; let (remaining, second_timestamp) = impl_inactive_timestamp(context, remaining, false)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -354,6 +385,7 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
warning_delay: first_timestamp warning_delay: first_timestamp
.warning_delay .warning_delay
.or(second_timestamp.warning_delay), .or(second_timestamp.warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -392,7 +424,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -408,6 +440,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
end_time: Some(second_time), end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater), repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay), warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }

View File

@@ -20,7 +20,6 @@ use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::IndentationLevel; use crate::types::IndentationLevel;
@@ -82,14 +81,21 @@ pub(crate) fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
// We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::"). // We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::").
let (remaining, _) = many_till( let (remaining, post_blank) = recognize(many_till(
one_of(" \t"), one_of(" \t"),
alt(( alt((
peek(recognize(none_of(" \t"))), peek(recognize(none_of(" \t"))),
parser_with_context!(exit_matcher_parser)(context), parser_with_context!(exit_matcher_parser)(context),
)), )),
)(input)?; ))(input)?;
Ok((remaining, None)) Ok((
remaining,
if post_blank.len() == 0 {
None
} else {
Some(post_blank)
},
))
} }
#[cfg_attr( #[cfg_attr(
@@ -129,9 +135,7 @@ pub(crate) fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()>
if input.is_at_start_of_line() { if input.is_at_start_of_line() {
Ok((input, ())) Ok((input, ()))
} else { } else {
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("Not at start of line")))
"Not at start of line",
))))
} }
} }
@@ -152,9 +156,9 @@ fn _preceded_by_whitespace<'s>(
.map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space .map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space
.unwrap_or(allow_start_of_file) .unwrap_or(allow_start_of_file)
{ {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Must be preceded by a whitespace character.", "Must be preceded by a whitespace character.",
)))); )));
} }
Ok((input, ())) Ok((input, ()))
} }
@@ -195,9 +199,7 @@ pub(crate) fn text_until_exit<'b, 'g, 'r, 's>(
#[allow(dead_code)] #[allow(dead_code)]
fn not_yet_implemented() -> Res<OrgSource<'static>, ()> { fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("Not implemented yet.")))
"Not implemented yet.",
))))
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -205,9 +207,7 @@ fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
/// Text from the current point until the next line break or end of file /// Text from the current point until the next line break or end of file
/// ///
/// Useful for debugging. /// Useful for debugging.
fn text_until_eol<'r, 's>( fn text_until_eol<'r, 's>(input: OrgSource<'s>) -> Result<&'s str, nom::Err<CustomError>> {
input: OrgSource<'s>,
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input) let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
.map(|(_remaining, line)| Into::<&str>::into(line))?; .map(|(_remaining, line)| Into::<&str>::into(line))?;
Ok(line.trim()) Ok(line.trim())
@@ -250,6 +250,10 @@ pub(crate) fn org_line_ending(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSou
} }
/// Match the whitespace at the beginning of a line and give it an indentation level. /// Match the whitespace at the beginning of a line and give it an indentation level.
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn indentation_level<'s>( pub(crate) fn indentation_level<'s>(
context: RefContext<'_, '_, '_, 's>, context: RefContext<'_, '_, '_, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,

View File

@@ -1,7 +1,9 @@
use super::macros::to_ast_node; use super::macros::to_ast_node;
use super::CenterBlock; use super::CenterBlock;
use super::PostBlank;
use super::QuoteBlock; use super::QuoteBlock;
use super::SpecialBlock; use super::SpecialBlock;
use super::StandardProperties;
use crate::types::AngleLink; use crate::types::AngleLink;
use crate::types::BabelCall; use crate::types::BabelCall;
use crate::types::Bold; use crate::types::Bold;
@@ -24,7 +26,6 @@ use crate::types::ExportSnippet;
use crate::types::FixedWidthArea; use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition; use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference; use crate::types::FootnoteReference;
use crate::types::GetStandardProperties;
use crate::types::Heading; use crate::types::Heading;
use crate::types::HorizontalRule; use crate::types::HorizontalRule;
use crate::types::InlineBabelCall; use crate::types::InlineBabelCall;
@@ -259,67 +260,193 @@ to_ast_node!(&'r Superscript<'s>, AstNode::Superscript);
to_ast_node!(&'r TableCell<'s>, AstNode::TableCell); to_ast_node!(&'r TableCell<'s>, AstNode::TableCell);
to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp); to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp);
impl<'r, 's> GetStandardProperties<'s> for AstNode<'r, 's> { impl<'r, 's> StandardProperties<'s> for AstNode<'r, 's> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn crate::types::StandardProperties<'s> { fn get_source<'b>(&'b self) -> &'s str {
match self { match self {
AstNode::Document(inner) => *inner, AstNode::Document(inner) => inner.get_source(),
AstNode::Heading(inner) => *inner, AstNode::Heading(inner) => inner.get_source(),
AstNode::Section(inner) => *inner, AstNode::Section(inner) => inner.get_source(),
AstNode::Paragraph(inner) => *inner, AstNode::Paragraph(inner) => inner.get_source(),
AstNode::PlainList(inner) => *inner, AstNode::PlainList(inner) => inner.get_source(),
AstNode::PlainListItem(inner) => *inner, AstNode::PlainListItem(inner) => inner.get_source(),
AstNode::CenterBlock(inner) => *inner, AstNode::CenterBlock(inner) => inner.get_source(),
AstNode::QuoteBlock(inner) => *inner, AstNode::QuoteBlock(inner) => inner.get_source(),
AstNode::SpecialBlock(inner) => *inner, AstNode::SpecialBlock(inner) => inner.get_source(),
AstNode::DynamicBlock(inner) => *inner, AstNode::DynamicBlock(inner) => inner.get_source(),
AstNode::FootnoteDefinition(inner) => *inner, AstNode::FootnoteDefinition(inner) => inner.get_source(),
AstNode::Comment(inner) => *inner, AstNode::Comment(inner) => inner.get_source(),
AstNode::Drawer(inner) => *inner, AstNode::Drawer(inner) => inner.get_source(),
AstNode::PropertyDrawer(inner) => *inner, AstNode::PropertyDrawer(inner) => inner.get_source(),
AstNode::NodeProperty(inner) => *inner, AstNode::NodeProperty(inner) => inner.get_source(),
AstNode::Table(inner) => *inner, AstNode::Table(inner) => inner.get_source(),
AstNode::TableRow(inner) => *inner, AstNode::TableRow(inner) => inner.get_source(),
AstNode::VerseBlock(inner) => *inner, AstNode::VerseBlock(inner) => inner.get_source(),
AstNode::CommentBlock(inner) => *inner, AstNode::CommentBlock(inner) => inner.get_source(),
AstNode::ExampleBlock(inner) => *inner, AstNode::ExampleBlock(inner) => inner.get_source(),
AstNode::ExportBlock(inner) => *inner, AstNode::ExportBlock(inner) => inner.get_source(),
AstNode::SrcBlock(inner) => *inner, AstNode::SrcBlock(inner) => inner.get_source(),
AstNode::Clock(inner) => *inner, AstNode::Clock(inner) => inner.get_source(),
AstNode::DiarySexp(inner) => *inner, AstNode::DiarySexp(inner) => inner.get_source(),
AstNode::Planning(inner) => *inner, AstNode::Planning(inner) => inner.get_source(),
AstNode::FixedWidthArea(inner) => *inner, AstNode::FixedWidthArea(inner) => inner.get_source(),
AstNode::HorizontalRule(inner) => *inner, AstNode::HorizontalRule(inner) => inner.get_source(),
AstNode::Keyword(inner) => *inner, AstNode::Keyword(inner) => inner.get_source(),
AstNode::BabelCall(inner) => *inner, AstNode::BabelCall(inner) => inner.get_source(),
AstNode::LatexEnvironment(inner) => *inner, AstNode::LatexEnvironment(inner) => inner.get_source(),
AstNode::Bold(inner) => *inner, AstNode::Bold(inner) => inner.get_source(),
AstNode::Italic(inner) => *inner, AstNode::Italic(inner) => inner.get_source(),
AstNode::Underline(inner) => *inner, AstNode::Underline(inner) => inner.get_source(),
AstNode::StrikeThrough(inner) => *inner, AstNode::StrikeThrough(inner) => inner.get_source(),
AstNode::Code(inner) => *inner, AstNode::Code(inner) => inner.get_source(),
AstNode::Verbatim(inner) => *inner, AstNode::Verbatim(inner) => inner.get_source(),
AstNode::PlainText(inner) => *inner, AstNode::PlainText(inner) => inner.get_source(),
AstNode::RegularLink(inner) => *inner, AstNode::RegularLink(inner) => inner.get_source(),
AstNode::RadioLink(inner) => *inner, AstNode::RadioLink(inner) => inner.get_source(),
AstNode::RadioTarget(inner) => *inner, AstNode::RadioTarget(inner) => inner.get_source(),
AstNode::PlainLink(inner) => *inner, AstNode::PlainLink(inner) => inner.get_source(),
AstNode::AngleLink(inner) => *inner, AstNode::AngleLink(inner) => inner.get_source(),
AstNode::OrgMacro(inner) => *inner, AstNode::OrgMacro(inner) => inner.get_source(),
AstNode::Entity(inner) => *inner, AstNode::Entity(inner) => inner.get_source(),
AstNode::LatexFragment(inner) => *inner, AstNode::LatexFragment(inner) => inner.get_source(),
AstNode::ExportSnippet(inner) => *inner, AstNode::ExportSnippet(inner) => inner.get_source(),
AstNode::FootnoteReference(inner) => *inner, AstNode::FootnoteReference(inner) => inner.get_source(),
AstNode::Citation(inner) => *inner, AstNode::Citation(inner) => inner.get_source(),
AstNode::CitationReference(inner) => *inner, AstNode::CitationReference(inner) => inner.get_source(),
AstNode::InlineBabelCall(inner) => *inner, AstNode::InlineBabelCall(inner) => inner.get_source(),
AstNode::InlineSourceBlock(inner) => *inner, AstNode::InlineSourceBlock(inner) => inner.get_source(),
AstNode::LineBreak(inner) => *inner, AstNode::LineBreak(inner) => inner.get_source(),
AstNode::Target(inner) => *inner, AstNode::Target(inner) => inner.get_source(),
AstNode::StatisticsCookie(inner) => *inner, AstNode::StatisticsCookie(inner) => inner.get_source(),
AstNode::Subscript(inner) => *inner, AstNode::Subscript(inner) => inner.get_source(),
AstNode::Superscript(inner) => *inner, AstNode::Superscript(inner) => inner.get_source(),
AstNode::TableCell(inner) => *inner, AstNode::TableCell(inner) => inner.get_source(),
AstNode::Timestamp(inner) => *inner, AstNode::Timestamp(inner) => inner.get_source(),
}
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
match self {
AstNode::Document(inner) => inner.get_contents(),
AstNode::Heading(inner) => inner.get_contents(),
AstNode::Section(inner) => inner.get_contents(),
AstNode::Paragraph(inner) => inner.get_contents(),
AstNode::PlainList(inner) => inner.get_contents(),
AstNode::PlainListItem(inner) => inner.get_contents(),
AstNode::CenterBlock(inner) => inner.get_contents(),
AstNode::QuoteBlock(inner) => inner.get_contents(),
AstNode::SpecialBlock(inner) => inner.get_contents(),
AstNode::DynamicBlock(inner) => inner.get_contents(),
AstNode::FootnoteDefinition(inner) => inner.get_contents(),
AstNode::Comment(inner) => inner.get_contents(),
AstNode::Drawer(inner) => inner.get_contents(),
AstNode::PropertyDrawer(inner) => inner.get_contents(),
AstNode::NodeProperty(inner) => inner.get_contents(),
AstNode::Table(inner) => inner.get_contents(),
AstNode::TableRow(inner) => inner.get_contents(),
AstNode::VerseBlock(inner) => inner.get_contents(),
AstNode::CommentBlock(inner) => inner.get_contents(),
AstNode::ExampleBlock(inner) => inner.get_contents(),
AstNode::ExportBlock(inner) => inner.get_contents(),
AstNode::SrcBlock(inner) => inner.get_contents(),
AstNode::Clock(inner) => inner.get_contents(),
AstNode::DiarySexp(inner) => inner.get_contents(),
AstNode::Planning(inner) => inner.get_contents(),
AstNode::FixedWidthArea(inner) => inner.get_contents(),
AstNode::HorizontalRule(inner) => inner.get_contents(),
AstNode::Keyword(inner) => inner.get_contents(),
AstNode::BabelCall(inner) => inner.get_contents(),
AstNode::LatexEnvironment(inner) => inner.get_contents(),
AstNode::Bold(inner) => inner.get_contents(),
AstNode::Italic(inner) => inner.get_contents(),
AstNode::Underline(inner) => inner.get_contents(),
AstNode::StrikeThrough(inner) => inner.get_contents(),
AstNode::Code(inner) => inner.get_contents(),
AstNode::Verbatim(inner) => inner.get_contents(),
AstNode::PlainText(inner) => inner.get_contents(),
AstNode::RegularLink(inner) => inner.get_contents(),
AstNode::RadioLink(inner) => inner.get_contents(),
AstNode::RadioTarget(inner) => inner.get_contents(),
AstNode::PlainLink(inner) => inner.get_contents(),
AstNode::AngleLink(inner) => inner.get_contents(),
AstNode::OrgMacro(inner) => inner.get_contents(),
AstNode::Entity(inner) => inner.get_contents(),
AstNode::LatexFragment(inner) => inner.get_contents(),
AstNode::ExportSnippet(inner) => inner.get_contents(),
AstNode::FootnoteReference(inner) => inner.get_contents(),
AstNode::Citation(inner) => inner.get_contents(),
AstNode::CitationReference(inner) => inner.get_contents(),
AstNode::InlineBabelCall(inner) => inner.get_contents(),
AstNode::InlineSourceBlock(inner) => inner.get_contents(),
AstNode::LineBreak(inner) => inner.get_contents(),
AstNode::Target(inner) => inner.get_contents(),
AstNode::StatisticsCookie(inner) => inner.get_contents(),
AstNode::Subscript(inner) => inner.get_contents(),
AstNode::Superscript(inner) => inner.get_contents(),
AstNode::TableCell(inner) => inner.get_contents(),
AstNode::Timestamp(inner) => inner.get_contents(),
}
}
fn get_post_blank(&self) -> PostBlank {
match self {
AstNode::Document(inner) => inner.get_post_blank(),
AstNode::Heading(inner) => inner.get_post_blank(),
AstNode::Section(inner) => inner.get_post_blank(),
AstNode::Paragraph(inner) => inner.get_post_blank(),
AstNode::PlainList(inner) => inner.get_post_blank(),
AstNode::PlainListItem(inner) => inner.get_post_blank(),
AstNode::CenterBlock(inner) => inner.get_post_blank(),
AstNode::QuoteBlock(inner) => inner.get_post_blank(),
AstNode::SpecialBlock(inner) => inner.get_post_blank(),
AstNode::DynamicBlock(inner) => inner.get_post_blank(),
AstNode::FootnoteDefinition(inner) => inner.get_post_blank(),
AstNode::Comment(inner) => inner.get_post_blank(),
AstNode::Drawer(inner) => inner.get_post_blank(),
AstNode::PropertyDrawer(inner) => inner.get_post_blank(),
AstNode::NodeProperty(inner) => inner.get_post_blank(),
AstNode::Table(inner) => inner.get_post_blank(),
AstNode::TableRow(inner) => inner.get_post_blank(),
AstNode::VerseBlock(inner) => inner.get_post_blank(),
AstNode::CommentBlock(inner) => inner.get_post_blank(),
AstNode::ExampleBlock(inner) => inner.get_post_blank(),
AstNode::ExportBlock(inner) => inner.get_post_blank(),
AstNode::SrcBlock(inner) => inner.get_post_blank(),
AstNode::Clock(inner) => inner.get_post_blank(),
AstNode::DiarySexp(inner) => inner.get_post_blank(),
AstNode::Planning(inner) => inner.get_post_blank(),
AstNode::FixedWidthArea(inner) => inner.get_post_blank(),
AstNode::HorizontalRule(inner) => inner.get_post_blank(),
AstNode::Keyword(inner) => inner.get_post_blank(),
AstNode::BabelCall(inner) => inner.get_post_blank(),
AstNode::LatexEnvironment(inner) => inner.get_post_blank(),
AstNode::Bold(inner) => inner.get_post_blank(),
AstNode::Italic(inner) => inner.get_post_blank(),
AstNode::Underline(inner) => inner.get_post_blank(),
AstNode::StrikeThrough(inner) => inner.get_post_blank(),
AstNode::Code(inner) => inner.get_post_blank(),
AstNode::Verbatim(inner) => inner.get_post_blank(),
AstNode::PlainText(inner) => inner.get_post_blank(),
AstNode::RegularLink(inner) => inner.get_post_blank(),
AstNode::RadioLink(inner) => inner.get_post_blank(),
AstNode::RadioTarget(inner) => inner.get_post_blank(),
AstNode::PlainLink(inner) => inner.get_post_blank(),
AstNode::AngleLink(inner) => inner.get_post_blank(),
AstNode::OrgMacro(inner) => inner.get_post_blank(),
AstNode::Entity(inner) => inner.get_post_blank(),
AstNode::LatexFragment(inner) => inner.get_post_blank(),
AstNode::ExportSnippet(inner) => inner.get_post_blank(),
AstNode::FootnoteReference(inner) => inner.get_post_blank(),
AstNode::Citation(inner) => inner.get_post_blank(),
AstNode::CitationReference(inner) => inner.get_post_blank(),
AstNode::InlineBabelCall(inner) => inner.get_post_blank(),
AstNode::InlineSourceBlock(inner) => inner.get_post_blank(),
AstNode::LineBreak(inner) => inner.get_post_blank(),
AstNode::Target(inner) => inner.get_post_blank(),
AstNode::StatisticsCookie(inner) => inner.get_post_blank(),
AstNode::Subscript(inner) => inner.get_post_blank(),
AstNode::Superscript(inner) => inner.get_post_blank(),
AstNode::TableCell(inner) => inner.get_post_blank(),
AstNode::Timestamp(inner) => inner.get_post_blank(),
} }
} }
} }

View File

@@ -1,9 +1,9 @@
use std::path::PathBuf; use std::path::PathBuf;
use super::Element; use super::Element;
use super::GetStandardProperties;
use super::NodeProperty; use super::NodeProperty;
use super::Object; use super::Object;
use super::PostBlank;
use super::StandardProperties; use super::StandardProperties;
use super::Timestamp; use super::Timestamp;
@@ -17,6 +17,7 @@ pub struct Document<'s> {
pub path: Option<PathBuf>, pub path: Option<PathBuf>,
pub zeroth_section: Option<Section<'s>>, pub zeroth_section: Option<Section<'s>>,
pub children: Vec<Heading<'s>>, pub children: Vec<Heading<'s>>,
pub contents: &'s str,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -34,11 +35,14 @@ pub struct Heading<'s> {
pub scheduled: Option<Timestamp<'s>>, pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>, pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>, pub closed: Option<Timestamp<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Section<'s> { pub struct Section<'s> {
pub source: &'s str, pub source: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
} }
@@ -54,41 +58,60 @@ pub enum TodoKeywordType {
Done, Done,
} }
impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self {
DocumentElement::Heading(inner) => inner,
DocumentElement::Section(inner) => inner,
}
}
}
impl<'s> StandardProperties<'s> for Document<'s> { impl<'s> StandardProperties<'s> for Document<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
0
}
} }
impl<'s> StandardProperties<'s> for Section<'s> { impl<'s> StandardProperties<'s> for Section<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.source)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Heading<'s> { impl<'s> StandardProperties<'s> for Heading<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> Heading<'s> { impl<'s> Heading<'s> {
pub fn get_raw_value(&self) -> String { pub fn get_raw_value(&self) -> String {
// TODO: I think this could just return a string slice instead of an owned string. // TODO: I think this could just return a string slice instead of an owned string.
let title_source: String = self let title_source: String = self.title.iter().map(|obj| obj.get_source()).collect();
.title
.iter()
.map(|obj| obj.get_standard_properties().get_source())
.collect();
title_source title_source
} }
@@ -132,3 +155,26 @@ impl<'s> Document<'s> {
.flat_map(|property_drawer| property_drawer.children.iter()) .flat_map(|property_drawer| property_drawer.children.iter())
} }
} }
impl<'s> StandardProperties<'s> for DocumentElement<'s> {
fn get_source<'b>(&'b self) -> &'s str {
match self {
DocumentElement::Heading(inner) => inner.get_source(),
DocumentElement::Section(inner) => inner.get_source(),
}
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
match self {
DocumentElement::Heading(inner) => inner.get_contents(),
DocumentElement::Section(inner) => inner.get_contents(),
}
}
fn get_post_blank(&self) -> PostBlank {
match self {
DocumentElement::Heading(inner) => inner.get_post_blank(),
DocumentElement::Section(inner) => inner.get_post_blank(),
}
}
}

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