Compare commits

..

170 Commits

Author SHA1 Message Date
Tom Alexander
b06798f23f Prepare for publishing to crates.io. 2023-12-21 19:40:28 -05:00
Tom Alexander
35dff5cdaf Rename to natter. 2023-12-21 19:28:31 -05:00
Tom Alexander
d641c8d638 Merge branch 'pretty' 2023-12-21 18:36:23 -05:00
Tom Alexander
7e2fd70212 Style the homepage. 2023-12-21 18:34:42 -05:00
Tom Alexander
4fb08bc7d0 Wrap inline footnote definitions in a paragraph tag.
This is to match the behavior of the upstream org html exporter.
2023-12-21 18:09:43 -05:00
Tom Alexander
01b55b7256 Make paragraphs under footnote definitions display inline.
This seems to be the behavior of the upstream org html exporter.
2023-12-21 17:44:40 -05:00
Tom Alexander
bd68681e44 Increase the size of headlines.
This is to make them stand out more from the regular text.
2023-12-21 17:31:19 -05:00
Tom Alexander
27ff13e675 Also special-case plain list items containing only paragraphs and sublists.
This seems to be the behavior of the upstream org html exporter.
2023-12-21 17:27:19 -05:00
Tom Alexander
2914e42ba1 For plain list items with a single child that is a paragraph, do not wrap in paragraph html tags.
This is mimicking the behavior from org-mode's HTML exporter.
2023-12-21 17:18:51 -05:00
Tom Alexander
a8969f141d Style code and verbatim. 2023-12-21 16:49:44 -05:00
Tom Alexander
6f049e00d4 Style inline source blocks. 2023-12-21 16:42:22 -05:00
Tom Alexander
45a1076d18 Style descriptive plain lists. 2023-12-21 16:39:26 -05:00
Tom Alexander
775c88d67a Style footnotes. 2023-12-21 15:44:56 -05:00
Tom Alexander
efbf6cfc0c Style ordered and unordered plain lists. 2023-12-21 15:31:13 -05:00
Tom Alexander
5af4a372ae Make object trailing space dependent on post_blank. 2023-12-21 15:13:07 -05:00
Tom Alexander
72952adb6b Add post_blank to the rust types. 2023-12-21 15:09:13 -05:00
Tom Alexander
8b85c02ef1 Wrap the intermediate Registry in an IntermediateContext.
This is currently just to maintain consistency with the render phase's RenderContext but in the future it should allow us to include immutable data that is not locked behind the ArcMutex.
2023-12-21 13:53:56 -05:00
Tom Alexander
2ae4839ce0 Remove common whitespace prefix from src block lines. 2023-12-21 13:16:05 -05:00
Tom Alexander
5654c40d03 Get the real language for src blocks from the org source. 2023-12-21 12:07:36 -05:00
Tom Alexander
b538750287 Preserve whitespace in src blocks. 2023-12-21 12:04:29 -05:00
Tom Alexander
65ed754bfe Space out some elements. 2023-12-19 21:57:33 -05:00
Tom Alexander
80cdf5166b Style quote blocks, h2, and h3. 2023-12-19 21:41:41 -05:00
Tom Alexander
3968121d54 Style src blocks. 2023-12-19 21:30:59 -05:00
Tom Alexander
a29b625631 Apply a dark background. 2023-12-19 20:33:21 -05:00
Tom Alexander
e193fcc2ba Start applying styles to blog posts. 2023-12-19 19:23:56 -05:00
Tom Alexander
050b426f6f Merge branch 'homepage' 2023-12-19 18:08:50 -05:00
Tom Alexander
da81f93e4a Re-enable the CSS reset sheet. 2023-12-19 18:06:52 -05:00
Tom Alexander
1581e5c401 Rename the output root directory in the render context. 2023-12-19 18:03:59 -05:00
Tom Alexander
2e1c979127 Add a prefix to footnote IDs.
This avoids a conflict with multiple blog posts rendering in the same stream.
2023-12-19 18:01:54 -05:00
Tom Alexander
d4b290ebe6 Remove unnecessary let statements from render macro. 2023-12-19 17:31:50 -05:00
Tom Alexander
4bb1f9983a Standardize the construction of intermediate BlogPostPage. 2023-12-19 17:31:16 -05:00
Tom Alexander
261fe8a1a2 Fix the links to the blog posts. 2023-12-19 16:49:46 -05:00
Tom Alexander
add267d616 Switch over to using the render context in render calls. 2023-12-19 16:20:12 -05:00
Tom Alexander
cb3278aba5 Create a struct that will combine all the context for converting intermediate objects into the dust render context. 2023-12-19 14:54:12 -05:00
Tom Alexander
94d9a95967 Add a basic template for the blog stream page. 2023-12-19 14:13:29 -05:00
Tom Alexander
6511115b95 Implement a flawed version of RenderBlogStreamEntry::new.
There are some shortcomings in this implementation listed in the comments, but this is the general structure.
2023-12-19 10:59:34 -05:00
Tom Alexander
53cd55932b Implement RenderBlogStream::new.
I still need to implement RenderBlogStreamEntry::new.
2023-12-19 10:47:03 -05:00
Tom Alexander
cbe2010407 Invoke dust to render the stream pages.
At this point the render_blog_stream function is done, but RenderBlogStream::new needs to be implemented to actually generate the render context. The body of this function should be similar to convert_blog_post_page_to_render_context.
2023-12-17 17:26:15 -05:00
Tom Alexander
2ba4a5e3d7 Generate newer and older links. 2023-12-17 17:16:26 -05:00
Tom Alexander
c3482cf1e4 Chunking the blog posts for the stream. 2023-12-17 16:57:37 -05:00
Tom Alexander
fdf84e3d0b Finding the index page. 2023-12-17 15:45:50 -05:00
Tom Alexander
0a4376dfb8 Rename intermediate blog post source file. 2023-12-17 15:32:07 -05:00
Tom Alexander
e8ed4a4f4a Initial structure for rendering a blog post stream. 2023-12-17 15:32:07 -05:00
Tom Alexander
60555999db TEMP: Disable the css reset.
This is so I can develop the header without having any CSS written yet since the page is a mess without CSS.
2023-12-17 14:56:23 -05:00
Tom Alexander
6968a5b02c Merge branch 'header' 2023-12-17 14:47:45 -05:00
Tom Alexander
c84cfdc02b Basic template for a page header. 2023-12-17 14:45:42 -05:00
Tom Alexander
c98489cacb Add the page header to the render context. 2023-12-17 14:45:42 -05:00
Tom Alexander
35dbab0ceb Create a page header struct. 2023-12-17 14:45:42 -05:00
Tom Alexander
1ff41940a5 Merge branch 'css' 2023-12-17 14:02:20 -05:00
Tom Alexander
884215a7e1 Writing the stylesheets to the output folder. 2023-12-17 13:46:47 -05:00
Tom Alexander
20c55f0708 Loading stylesheets from the default environment. 2023-12-17 12:43:47 -05:00
Tom Alexander
9e3d72972c Add a CSS reset file. 2023-12-17 11:55:38 -05:00
Tom Alexander
806c45a453 Switch back to the published version of organic. 2023-12-17 11:54:59 -05:00
Tom Alexander
270c42a509 Update to use get_value from the latest organic code. 2023-10-31 22:03:22 -04:00
Tom Alexander
e8963e107b Merge branch 'table' 2023-10-31 20:38:36 -04:00
Tom Alexander
0b64551a23 Add templates for tables. 2023-10-31 20:31:36 -04:00
Tom Alexander
b654ca4859 Add render phase to tables. 2023-10-31 20:29:37 -04:00
Tom Alexander
386af57ce6 Add intermediate stage for tables. 2023-10-31 20:26:34 -04:00
Tom Alexander
ef4d315bf2 Run cargo fix. 2023-10-31 20:02:46 -04:00
Tom Alexander
2142b01967 Merge branch 'text_markup' 2023-10-31 20:02:13 -04:00
Tom Alexander
159d8fb72a Add render stage for text markup. 2023-10-31 20:02:04 -04:00
Tom Alexander
0fae417610 Add intermediate stage for text markup. 2023-10-31 19:57:04 -04:00
Tom Alexander
ae933b491e Use macros for the intermediate to render step.
This is largely to make changing the type signature of these functions easier by significantly reducing the amount of places that duplicates the signature.
2023-10-31 19:48:05 -04:00
Tom Alexander
5e476e189a Merge branch 'latex_fragment' 2023-10-31 19:18:20 -04:00
Tom Alexander
533997dbf5 Add a template for latex fragment. 2023-10-31 19:16:13 -04:00
Tom Alexander
8695cf17c5 Copy the value of latex fragments through the pipeline. 2023-10-31 19:14:36 -04:00
Tom Alexander
e2f9938437 Add notes about latex fragments.
The places they are currently occurring in my posts are unintentional, so I don't think I will be implementing latex fragments right now, but the notes are worth keeping.
2023-10-31 12:32:20 -04:00
Tom Alexander
ada11816fb Run cargo fix. 2023-10-29 22:31:29 -04:00
Tom Alexander
ff57242434 Merge branch 'footnote' 2023-10-29 22:29:38 -04:00
Tom Alexander
0da37b25e3 Assign the reference counts. 2023-10-29 22:29:29 -04:00
Tom Alexander
ff03140007 Order the footnotes based on when they start processing rather than when they finish.
This has the benefit of making the output order make sense when footnote definitions reference footnote references but I mostly did it to match the behavior of upstream org-mode.
2023-10-29 22:29:29 -04:00
Tom Alexander
0ae492f8d3 Promote waiting footnote definitions. 2023-10-29 22:29:29 -04:00
Tom Alexander
613d49c6ec Record on-deck footnote definitions. 2023-10-29 22:29:29 -04:00
Tom Alexander
0f7e5eea25 no-op the template for footnote definitions. 2023-10-29 22:29:29 -04:00
Tom Alexander
2e7cfd5637 Only parse footnote definitions if they target a reference that exists. 2023-10-29 22:29:28 -04:00
Tom Alexander
afe62de2b6 Re-enable AstNode. 2023-10-29 22:29:28 -04:00
Tom Alexander
1f3b5262b8 Fix build by making the registry guarded by an ArcMutex. 2023-10-29 22:29:28 -04:00
Tom Alexander
f63620b547 Registry being mutuably borrowed. 2023-10-29 22:29:28 -04:00
Tom Alexander
671159cb82 Does not work even without IntoAstNode. 2023-10-29 22:29:28 -04:00
Tom Alexander
f1e985fb32 Lifetime issue. 2023-10-29 22:29:28 -04:00
Tom Alexander
a966be8122 Remove closures from iselector macro. 2023-10-29 22:29:28 -04:00
Tom Alexander
24b9782146 Avoid closures for the intermediate macro. 2023-10-29 22:29:28 -04:00
Tom Alexander
3d44d20384 Running into borrow issue on intermediate. 2023-10-29 18:46:14 -04:00
Tom Alexander
f98a09bc59 Use macros for creating the intermediate stage.
This is to make it easier to change function signatures by consolidating the places where the signatures exist.
2023-10-29 18:46:14 -04:00
Tom Alexander
ba511b7f9e Add another test for footnote definitions. 2023-10-29 18:46:13 -04:00
Tom Alexander
3720558d93 Render the footnote definitions. 2023-10-29 18:46:13 -04:00
Tom Alexander
b66ec507ef Create a render ast node type. 2023-10-29 18:46:13 -04:00
Tom Alexander
645ae26701 Remove intermediate lifetime. 2023-10-29 14:53:11 -04:00
Tom Alexander
6109902945 Create an intermediate ast node type. 2023-10-29 14:45:02 -04:00
Tom Alexander
cd27869122 Populate render context for footnote references. 2023-10-29 14:45:02 -04:00
Tom Alexander
795945f0da Register footnote definitions. 2023-10-29 14:45:02 -04:00
Tom Alexander
cb7c28c1ae Add intermediate lifetime. 2023-10-29 12:24:49 -04:00
Tom Alexander
52ca300de3 Beginning to hand out footnote ids. 2023-10-29 12:24:49 -04:00
Tom Alexander
06dcd22e69 Switch to split inclusive instead of manually-implemented full lines iterator. 2023-10-29 10:59:33 -04:00
Tom Alexander
f87c453459 Move the full lines iterator to its own file. 2023-10-29 10:55:03 -04:00
Tom Alexander
e42edb3f49 Merge branch 'src_block' 2023-10-29 10:44:42 -04:00
Tom Alexander
6d83828012 Add inline source block. 2023-10-29 10:44:32 -04:00
Tom Alexander
eaea37f448 Add a basic template for source blocks. 2023-10-29 10:41:03 -04:00
Tom Alexander
fb99fd2b39 Get the source code lines. 2023-10-29 10:32:05 -04:00
Tom Alexander
313313ae53 Initial structure for unhighlighted source blocks. 2023-10-29 09:37:27 -04:00
Tom Alexander
d9a3b13780 Use raw_link instead of path for regular links. 2023-10-29 09:31:00 -04:00
Tom Alexander
8d9a50226a Add a tree-sitter-highlight dependency for source blocks. 2023-10-27 21:17:00 -04:00
Tom Alexander
f164838953 Add a space after objects. 2023-10-27 20:54:24 -04:00
Tom Alexander
fe3f2642fe Add a marker to make incomplete templates more obvious in the rendered html. 2023-10-27 20:51:27 -04:00
Tom Alexander
3d89492518 Add regular link. 2023-10-27 20:43:57 -04:00
Tom Alexander
793789bdf2 Add entity. 2023-10-27 20:27:50 -04:00
Tom Alexander
dbea9318e9 Merge branch 'plain_list' 2023-10-27 20:21:30 -04:00
Tom Alexander
4adaeb0341 Add line break. 2023-10-27 20:21:21 -04:00
Tom Alexander
bfc9e3ed80 Add plain list items. 2023-10-27 20:18:19 -04:00
Tom Alexander
62ffc76376 Add basic templates for plain list. 2023-10-27 20:12:56 -04:00
Tom Alexander
5bbb12327b Add template for quote blocks. 2023-10-27 19:53:18 -04:00
Tom Alexander
f6c475c80c Add templates for text markup. 2023-10-27 19:42:33 -04:00
Tom Alexander
4c3bea06d1 Add templates for target, keyword, comment block and comment. 2023-10-27 19:30:16 -04:00
Tom Alexander
0e9d74b2c8 Merge branch 'element_template' 2023-10-27 19:26:38 -04:00
Tom Alexander
6bf1480366 Add a template for paragraph. 2023-10-27 19:26:27 -04:00
Tom Alexander
c279bad13a Add children to heading. 2023-10-27 19:22:17 -04:00
Tom Alexander
bd982fb62d Add a template for section. 2023-10-27 18:30:29 -04:00
Tom Alexander
53a531f568 Add dispatcher for object and element. 2023-10-27 18:26:56 -04:00
Tom Alexander
67b60087af Add templates for the ast nodes. 2023-10-27 18:15:53 -04:00
Tom Alexander
8a3b85d5fa Merge branch 'element_noop' 2023-10-27 17:49:04 -04:00
Tom Alexander
b0ac14ee58 Add the skeletons for the objects. 2023-10-27 17:48:19 -04:00
Tom Alexander
23713a934c Add the skeletons for the elements. 2023-10-27 17:08:58 -04:00
Tom Alexander
860b601f62 Merge branch 'build_site' 2023-10-27 16:19:39 -04:00
Tom Alexander
354d24cf69 Add comment as a no-op. 2023-10-27 16:14:37 -04:00
Tom Alexander
5891ac7fb7 Add keyword and as no-op. 2023-10-27 16:09:44 -04:00
Tom Alexander
f9377d7609 Make converstion to intermediate state async.
We are going to need to do things like call external tools for syntax highlighting so we are going to need async in there eventually.
2023-10-27 15:55:19 -04:00
Tom Alexander
4a6948cde7 Add paragraph. 2023-10-27 15:46:56 -04:00
Tom Alexander
5b34942b64 Add element. 2023-10-27 15:46:56 -04:00
Tom Alexander
7b01230234 Add target. 2023-10-27 15:05:50 -04:00
Tom Alexander
c6cf5f75ac Introduce a registry into the conversion to intermediate format. 2023-10-27 15:05:49 -04:00
Tom Alexander
e3b5f7f74f Rename blog_post module to intermediate.
This module is mostly the intermediate representation of the AST, so the renaming is to make that more clear. The three forms are parsed => intermediate => render.

Parsed comes from Organic and is a direct translation of the org-mode text.

Intermediate converts the parsed data into owned values and does any calculations that are needed on the data (for example: assigning numbers to footnotes.)

Render takes intermediate and translates it into the format expected by the dust templates. The processing in this step should be minimal since all the logic should be in the intermediate step.
2023-10-27 13:10:21 -04:00
Tom Alexander
1ac39c2a6f Add RenderPlainText. 2023-10-27 13:01:45 -04:00
Tom Alexander
744d3e50fb Convert intermediate objects into render objects. 2023-10-27 12:47:12 -04:00
Tom Alexander
4c59011389 Copy heading level. 2023-10-27 12:14:07 -04:00
Tom Alexander
ba2756c762 Create intermediate representation for plain text. 2023-10-27 10:23:05 -04:00
Tom Alexander
31a3efe417 Only print the contexts.
This allows us to pipe the output to jq to see the context easier. We can see the rendered output in the files written to disk.
2023-10-24 00:51:28 -04:00
Tom Alexander
2b7a19a1d4 Introduce the corresponding non-render types. 2023-10-24 00:36:08 -04:00
Tom Alexander
3b472a9e96 Introduce element and object enums. 2023-10-24 00:04:44 -04:00
Tom Alexander
77f8375d7a Introduce an array of document elements. 2023-10-24 00:01:40 -04:00
Tom Alexander
3cfcae25a9 Move the render context to its own folder.
We are going to have a lot of render context types because there are so many org-mode elements/objects so I'm moving it to a separate folder for organization.
2023-10-23 23:49:35 -04:00
Tom Alexander
448e9bb8c6 Add comments. 2023-10-23 23:36:47 -04:00
Tom Alexander
2413923b3f Render the page title and self link. 2023-10-23 23:06:14 -04:00
Tom Alexander
ab36a60545 Generate the minimum relative path by chopping off the shared stem. 2023-10-23 23:04:05 -04:00
Tom Alexander
68cae57f16 Add a function to get the shared portion of a path. 2023-10-23 22:50:43 -04:00
Tom Alexander
11bfb6836f Include a self-link for the blog. 2023-10-23 22:38:00 -04:00
Tom Alexander
3ac7826d2c Move the logic into convert_blog_post_page_to_render_context.
I was writing it in the build command's rust files for convenience, but now its getting long enough to warrant moving it into its final location.
2023-10-23 22:10:26 -04:00
Tom Alexander
178ce877bc Render the head for the page. 2023-10-23 21:51:15 -04:00
Tom Alexander
2b6f86d4e9 Switch to rendering blog post pages instead of blog posts. 2023-10-23 20:30:43 -04:00
Tom Alexander
dc233d26b1 Store the title in BlogPostPage. 2023-10-23 18:39:54 -04:00
Tom Alexander
e543a5db74 Starting to introduce a BlogPostPage struct.
Blog posts are going to be constructed of multiple documents each forming their own page. This will allow me to link to supporting documents without having to promote them to their own pages.
2023-10-23 16:03:37 -04:00
Tom Alexander
199621b6f1 Remove the separation of the main template.
I don't think this is necessary, and it certainly isn't necessary at this level.
2023-10-22 18:39:05 -04:00
Tom Alexander
586fd8a066 Getting rendered output from duster. 2023-10-22 18:31:56 -04:00
Tom Alexander
043cc5eda4 I think I have worked around the lifetime issue by keeping references to the intermediate str's. 2023-10-22 18:12:45 -04:00
Tom Alexander
58aba8efd5 Invoking the compile function.
I am going to have to address the lifetime issue of "compiled" duster templates borrowing the input str.
2023-10-22 17:37:27 -04:00
Tom Alexander
ce0819e85b Feeding the templates into the renderer integration. 2023-10-22 17:31:12 -04:00
Tom Alexander
fc5342adce Make the renderer a bit more generic. 2023-10-22 16:40:58 -04:00
Tom Alexander
aed88cf05a Add include_dir.
This will let us embed the default versions of templates, stylesheets, javascript, etc into the binary. Naturally, we will eventually support overriding the defaults.
2023-10-22 16:28:54 -04:00
Tom Alexander
24bac982f1 Starting to create the renderer integrations.
These are the layer directly over dust which can be used by anything, not just blog posts.
2023-10-22 16:26:43 -04:00
Tom Alexander
5f34cb2dd5 Creating a SiteRenderer struct to handle the logic for invoking dust. 2023-10-22 16:10:41 -04:00
Tom Alexander
0b6900eeca Serialize the RenderBlogPost to JSON.
This struct still does not contain anything, but I'm just setting up the skeleton for this code.
2023-10-22 16:01:42 -04:00
Tom Alexander
b72aec9d20 Starting a struct for what will be passed as the context into dust. 2023-10-22 15:31:45 -04:00
Tom Alexander
a510d0809f Add serde_json. 2023-10-22 15:06:31 -04:00
Tom Alexander
87d32323f2 Add duster. 2023-10-22 15:01:30 -04:00
Tom Alexander
a0c5b2d852 Don't use walkdir for getting the post directories.
We are only iterating a single level of depth anyway, so read_dir is enough.
2023-10-22 14:49:08 -04:00
Tom Alexander
a9fbb4cd63 Get the output directory and clear it. 2023-10-22 14:40:59 -04:00
Tom Alexander
07e4209048 Setting the post id based on the folder name. 2023-10-22 13:50:11 -04:00
Tom Alexander
2f0f3ab346 Switch to using CustomError because a boxed StdError is not Send. 2023-10-22 13:44:03 -04:00
Tom Alexander
d8fc49797e Moving into a load_blog_post function to create a BlogPost struct. 2023-10-22 12:04:09 -04:00
Tom Alexander
816780589f Parse the org-mode files. 2023-10-21 18:00:51 -04:00
Tom Alexander
acaa12cb6e Read the org files inside the writer directory. 2023-10-20 20:16:22 -04:00
227 changed files with 6633 additions and 43 deletions

278
Cargo.lock generated
View File

@@ -65,6 +65,18 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.69"
@@ -80,6 +92,24 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "0.19.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.5.0" version = "1.5.0"
@@ -146,12 +176,118 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "duster"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17d19cdf8e1ae4aead0978a30e99817af1c36abaf22f81901952b1310abb6989"
dependencies = [
"nom 6.1.2",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "funty"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futures"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
[[package]]
name = "futures-executor"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
[[package]]
name = "futures-macro"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
[[package]]
name = "futures-task"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
[[package]]
name = "futures-util"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.28.0" version = "0.28.0"
@@ -176,6 +312,25 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "include_dir"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f"
dependencies = [
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.0.2" version = "2.0.2"
@@ -186,6 +341,25 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if",
"ryu",
"static_assertions",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.149" version = "0.2.149"
@@ -213,6 +387,35 @@ dependencies = [
"adler", "adler",
] ]
[[package]]
name = "natter"
version = "0.0.1"
dependencies = [
"clap",
"duster",
"futures",
"include_dir",
"organic",
"serde",
"serde_json",
"tokio",
"toml",
"walkdir",
]
[[package]]
name = "nom"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
dependencies = [
"bitvec",
"funty",
"lexical-core",
"memchr",
"version_check",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@@ -244,11 +447,11 @@ dependencies = [
[[package]] [[package]]
name = "organic" name = "organic"
version = "0.1.12" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f0f8a2a6d31c3cac7ebf543d8cb2e8f648300462fc2f6b1a09cac10daf0387" checksum = "c61b01691695303b42f9a2ff318bec83853fbeb65c96569f2fb391e7636801c6"
dependencies = [ dependencies = [
"nom", "nom 7.1.3",
"walkdir", "walkdir",
] ]
@@ -258,6 +461,12 @@ version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.69" version = "1.0.69"
@@ -276,12 +485,24 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "radium"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@@ -311,6 +532,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.3" version = "0.6.3"
@@ -320,6 +552,21 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.38" version = "2.0.38"
@@ -331,6 +578,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.33.0" version = "1.33.0"
@@ -389,6 +642,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.4.0" version = "2.4.0"
@@ -506,12 +765,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "writer" name = "wyz"
version = "0.0.1" version = "0.2.0"
dependencies = [ source = "registry+https://github.com/rust-lang/crates.io-index"
"clap", checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
"organic",
"serde",
"tokio",
"toml",
]

View File

@@ -1,16 +1,34 @@
[package] [package]
name = "writer" name = "natter"
version = "0.0.1" version = "0.0.1"
edition = "2021" edition = "2021"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "A static site generator using org source files."
license = "0BSD"
repository = "https://code.fizz.buzz/talexander/natter"
readme = "README.md"
keywords = ["static", "site", "generator"]
categories = ["command-line-utilities"]
resolver = "2"
include = [
"LICENSE",
"**/*.rs",
"Cargo.toml",
"Cargo.lock",
"default_environment/"
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
# error-context, suggestions, usage | env
clap = { version = "4.4.6", default-features = false, features = ["std", "color", "help", "derive"] } clap = { version = "4.4.6", default-features = false, features = ["std", "color", "help", "derive"] }
organic = "0.1.12" duster = "0.1.1"
# | alloc, rc, serde_derive, unstable futures = "0.3.29"
include_dir = "0.7.3"
# TODO: This is temporary to work on the latest organic code. Eventually switch back to using the published crate.
# organic = { path = "../organic" }
organic = "0.1.13"
serde = { version = "1.0.189", default-features = false, features = ["std", "derive"] } serde = { version = "1.0.189", default-features = false, features = ["std", "derive"] }
serde_json = "1.0.107"
tokio = { version = "1.30.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util"] } tokio = { version = "1.30.0", default-features = false, features = ["rt", "rt-multi-thread", "fs", "io-util"] }
# display, parse | indexmap, preserve_order
toml = "0.8.2" toml = "0.8.2"
walkdir = "2.4.0"

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# Natter
Natter is a static site generator for blogs using org as an input format. It is at a very early stage, and will have deep sweeping changes.

View File

@@ -0,0 +1,187 @@
:root {
--main-max-width: 800px;
--site-background-color: #0a0a0a;
--site-text-color: #fffffc;
--header-divider-color: #6a687a;
--stream-divider-color: #6ccff6;
--src-font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
--src-block-background-color: #141414;
--src-block-border-color: #84828f;
--src-block-language-color: #0a0a0a;
--src-block-language-background: #84828f;
--quote-block-border-color: #84828f;
}
body {
color: var(--site-text-color);
background-color: var(--site-background-color);
font-family: source-sans-pro, Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', sans-serif;
a:link, a:visited {
/* TODO: Should I use a different color for links? */
color: var(--site-text-color);
}
}
.page_centering {
display: flex;
flex-direction: column;
align-items: center;
}
.page_header {
width: 100%;
max-width: var(--main-max-width);
border-bottom: 0.1rem solid var(--header-divider-color);
.home_link {
font-size: 1.2rem;
font-weight: 600;
text-decoration: none;
&:link, &:visited {
color: var(--site-text-color);
}
}
}
.main_content {
width: 100%;
max-width: var(--main-max-width);
font-size: 1.2rem;
line-height: 1.2;
/* A stand-alone blog post (not in a blog stream). */
.blog_post {
padding: 1rem 0 3rem 0;
}
.blog_stream {
.stream_divider {
color: var(--stream-divider-color);
}
}
/* A blog post in a blog stream (for example, the homepage). */
.blog_stream_post {
background: #1F1F1F;
padding: 1rem 0.2rem;
}
.blog_post_title {
font-size: 2.5rem;
font-weight: 700;
padding-bottom: 1rem;
}
p {
margin: 1rem 0;
}
.src_block {
background: var(--src-block-background-color);
border-radius: 3px;
border: 1px solid var(--src-block-border-color);
font-size: 1rem;
font-family: var(--src-font-family);
margin: 1rem 0;
.src_language {
display: inline-block;
color: var(--src-block-language-color);
background: var(--src-block-language-background);
border-radius: 0 0 3px 0;
padding: 0.1rem 0.5rem;
font-size: 0.8rem;
vertical-align: top;
}
.src_body {
margin: 0.5rem;
.src_line {
white-space: pre-wrap;
}
}
}
.inline_source_block {
font-family: var(--src-font-family);
font-size: 1rem;
}
.code, .verbatim {
font-family: var(--src-font-family);
font-size: 1rem;
}
.quote_block {
border-left: 1px solid var(--quote-block-border-color);
padding: 0 0 0 1rem;
margin: 1rem 0 1rem 2rem;
}
h2, h3 {
margin: 1rem 0;
padding-bottom: 0.5rem;
}
h2 {
font-size: 2.3rem;
font-weight: 600;
}
h3 {
font-size: 2.1rem;
font-weight: 600;
}
.plain_list {
&.unordered {
list-style-type: disc;
padding-left: 2.5rem;
}
&.ordered {
list-style-type: decimal;
padding-left: 2.5rem;
}
&.descriptive {
font-size: 1rem;
> dt {
font-weight: 600;
}
> dd {
padding-left: 2.5rem;
}
}
}
.footnote_reference {
vertical-align: super;
font-size: 80%;
> a {
text-decoration: none;
}
}
.footnote_definition {
.label {
text-decoration: none;
}
.definition {
display: inline;
> p {
display: inline;
}
}
}
}

View File

@@ -0,0 +1,48 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

View File

@@ -0,0 +1 @@
!!!!!!!! angle_link

View File

@@ -0,0 +1,57 @@
{@select key=.type}
{@eq value="heading"}{>heading/}{/eq}
{@eq value="section"}{>section/}{/eq}
{@eq value="paragraph"}{>paragraph/}{/eq}
{@eq value="plain_list"}{>plain_list/}{/eq}
{@eq value="center_block"}{>center_block/}{/eq}
{@eq value="quote_block"}{>quote_block/}{/eq}
{@eq value="special_block"}{>special_block/}{/eq}
{@eq value="dynamic_block"}{>dynamic_block/}{/eq}
{@eq value="footnote_definition"}{>footnote_definition/}{/eq}
{@eq value="comment"}{>comment/}{/eq}
{@eq value="drawer"}{>drawer/}{/eq}
{@eq value="property_drawer"}{>property_drawer/}{/eq}
{@eq value="table"}{>table/}{/eq}
{@eq value="verse_block"}{>verse_block/}{/eq}
{@eq value="comment_block"}{>comment_block/}{/eq}
{@eq value="example_block"}{>example_block/}{/eq}
{@eq value="export_block"}{>export_block/}{/eq}
{@eq value="src_block"}{>src_block/}{/eq}
{@eq value="clock"}{>clock/}{/eq}
{@eq value="diary_sexp"}{>diary_sexp/}{/eq}
{@eq value="planning"}{>planning/}{/eq}
{@eq value="fixed_width_area"}{>fixed_width_area/}{/eq}
{@eq value="horizontal_rule"}{>horizontal_rule/}{/eq}
{@eq value="keyword"}{>keyword/}{/eq}
{@eq value="babel_call"}{>babel_call/}{/eq}
{@eq value="latex_environment"}{>latex_environment/}{/eq}
{@eq value="bold"}{>bold/}{/eq}
{@eq value="italic"}{>italic/}{/eq}
{@eq value="underline"}{>underline/}{/eq}
{@eq value="strike_through"}{>strike_through/}{/eq}
{@eq value="code"}{>code/}{/eq}
{@eq value="verbatim"}{>verbatim/}{/eq}
{@eq value="plain_text"}{>plain_text/}{/eq}
{@eq value="regular_link"}{>regular_link/}{/eq}
{@eq value="radio_link"}{>radio_link/}{/eq}
{@eq value="radio_target"}{>radio_target/}{/eq}
{@eq value="plain_link"}{>plain_link/}{/eq}
{@eq value="angle_link"}{>angle_link/}{/eq}
{@eq value="org_macro"}{>org_macro/}{/eq}
{@eq value="entity"}{>entity/}{/eq}
{@eq value="latex_fragment"}{>latex_fragment/}{/eq}
{@eq value="export_snippet"}{>export_snippet/}{/eq}
{@eq value="footnote_reference"}{>footnote_reference/}{/eq}
{@eq value="citation"}{>citation/}{/eq}
{@eq value="citation_reference"}{>citation_reference/}{/eq}
{@eq value="inline_babel_call"}{>inline_babel_call/}{/eq}
{@eq value="inline_source_block"}{>inline_source_block/}{/eq}
{@eq value="line_break"}{>line_break/}{/eq}
{@eq value="target"}{>target/}{/eq}
{@eq value="statistics_cookie"}{>statistics_cookie/}{/eq}
{@eq value="subscript"}{>subscript/}{/eq}
{@eq value="superscript"}{>superscript/}{/eq}
{@eq value="timestamp"}{>timestamp/}{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized type {.type}.{/none}
{/select}
{! TODO: Maybe the final space should be conditional on end blank in the org source !}

View File

@@ -0,0 +1 @@
!!!!!!!! babel_call

View File

@@ -0,0 +1,19 @@
<article class="blog_post">
{?.title}<h1 class="blog_post_title"><span>{.title}</span></h1>{/.title}
{! TODO: date? !}
{! TODO: Table of contents? !}
<div class="blog_post_body">
{#.children}
{>document_element/}
{/.children}
{?.footnotes}
<h2>Footnotes:</h2>
{#.footnotes}
{>real_footnote_definition/}
{/.footnotes}
{/.footnotes}
</div>
</article>

View File

@@ -0,0 +1,32 @@
<div class="blog_stream">
{#.children}
{@gt key=$idx value=0}<hr class="stream_divider" />{/gt}
<div class="blog_stream_post">
<div class="blog_post_intro">
{?.title}{?.self_link}<a class="blog_post_title" href="{.self_link}">{.title}</a>{:else}<div class="blog_post_title">{.title}</div>{/.self_link}{/.title}
{! TODO: date? !}
</div>
{! TODO: Table of contents? !}
<div class="blog_post_body">
{#.children}
{>document_element/}
{/.children}
{?.footnotes}
<h2>Footnotes:</h2>
{#.footnotes}
{>real_footnote_definition/}
{/.footnotes}
{/.footnotes}
</div>
</div>
{/.children}
{#.stream_pagination}
<div class="stream_nav">
{?.older_link}<a href="{.older_link}">Older</a>{/.older_link}
{?.newer_link}<a href="{.newer_link}">Newer</a>{/.newer_link}
</div>
{/.stream_pagination}
</div>

View File

@@ -0,0 +1,3 @@
<b>{#.children}
{>object/}
{/.children}</b>

View File

@@ -0,0 +1 @@
!!!!!!!! center_block

View File

@@ -0,0 +1 @@
!!!!!!!! citation

View File

@@ -0,0 +1 @@
!!!!!!!! citation_reference

View File

@@ -0,0 +1 @@
!!!!!!!! clock

View File

@@ -0,0 +1 @@
<code class="code">{.contents}</code>

View File

@@ -0,0 +1 @@
{! noop !}

View File

@@ -0,0 +1 @@
{! noop !}

View File

@@ -0,0 +1 @@
!!!!!!!! diary_sexp

View File

@@ -0,0 +1,5 @@
{@select key=.type}
{@eq value="heading"}{>heading/}{/eq}
{@eq value="section"}{>section/}{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized type {.type}.{/none}
{/select}

View File

@@ -0,0 +1 @@
!!!!!!!! drawer

View File

@@ -0,0 +1 @@
!!!!!!!! dynamic_block

View File

@@ -0,0 +1,28 @@
{@select key=.type}
{@eq value="paragraph"}{>paragraph/}{/eq}
{@eq value="plain_list"}{>plain_list/}{/eq}
{@eq value="plain_list_simple_item"}{>plain_list_simple_item/}{/eq}
{@eq value="center_block"}{>center_block/}{/eq}
{@eq value="quote_block"}{>quote_block/}{/eq}
{@eq value="special_block"}{>special_block/}{/eq}
{@eq value="dynamic_block"}{>dynamic_block/}{/eq}
{@eq value="footnote_definition"}{>footnote_definition/}{/eq}
{@eq value="comment"}{>comment/}{/eq}
{@eq value="drawer"}{>drawer/}{/eq}
{@eq value="property_drawer"}{>property_drawer/}{/eq}
{@eq value="table"}{>table/}{/eq}
{@eq value="verse_block"}{>verse_block/}{/eq}
{@eq value="comment_block"}{>comment_block/}{/eq}
{@eq value="example_block"}{>example_block/}{/eq}
{@eq value="export_block"}{>export_block/}{/eq}
{@eq value="src_block"}{>src_block/}{/eq}
{@eq value="clock"}{>clock/}{/eq}
{@eq value="diary_sexp"}{>diary_sexp/}{/eq}
{@eq value="planning"}{>planning/}{/eq}
{@eq value="fixed_width_area"}{>fixed_width_area/}{/eq}
{@eq value="horizontal_rule"}{>horizontal_rule/}{/eq}
{@eq value="keyword"}{>keyword/}{/eq}
{@eq value="babel_call"}{>babel_call/}{/eq}
{@eq value="latex_environment"}{>latex_environment/}{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized type {.type}.{/none}
{/select}

View File

@@ -0,0 +1 @@
{.html|s}

View File

@@ -0,0 +1 @@
!!!!!!!! example_block

View File

@@ -0,0 +1 @@
!!!!!!!! export_block

View File

@@ -0,0 +1 @@
!!!!!!!! export_snippet

View File

@@ -0,0 +1 @@
!!!!!!!! fixed_width_area

View File

@@ -0,0 +1 @@
{! noop !}

View File

@@ -0,0 +1 @@
<sup class="footnote_reference"><a id="{.reference_id}" href="{.definition_link}">{.label}</a></sup>

View File

@@ -0,0 +1 @@
!!!!!!!! global_settings

View File

@@ -0,0 +1,11 @@
{@lte key=.level value=4}
<h{.level}>{#.title}{>object/}{/.title}</h{.level}>
{:else}
<ol>
<li>{#.title}{>object/}{/.title}</li>
</ol>
{/lte}
{#.children}
{>document_element/}
{/.children}

View File

@@ -0,0 +1 @@
!!!!!!!! horizontal_rule

View File

@@ -0,0 +1 @@
!!!!!!!! inline_babel_call

View File

@@ -0,0 +1 @@
<code class="inline_source_block">{.value}</code>

View File

@@ -0,0 +1,3 @@
<i>{#.children}
{>object/}
{/.children}</i>

View File

@@ -0,0 +1 @@
{! noop !}

View File

@@ -0,0 +1 @@
!!!!!!!! latex_environment

View File

@@ -0,0 +1,2 @@
{! TODO: Should I be including MathJax somewhere? !}
{.value}

View File

@@ -0,0 +1 @@
<br/>

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
{#global_settings.css_files}<link rel="stylesheet" href="{.}">{/global_settings.css_files}
{#global_settings.js_files}<script type="text/javascript" src="{.}"></script>{/global_settings.js_files}
{?global_settings.page_title}<title>{global_settings.page_title}</title>{/global_settings.page_title}
</head>
<body class="page_centering">
{#.page_header}{>page_header/}{/.page_header}
<main class="main_content">
{@select key=.type}
{@eq value="blog_post_page"}{>blog_post_page/}{/eq}
{@eq value="blog_stream"}{>blog_stream/}{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized page content type{/none}
{/select}
</main>
</body>
</html>

View File

@@ -0,0 +1,30 @@
{@select key=.type}
{@eq value="bold"}{>bold/}{/eq}
{@eq value="italic"}{>italic/}{/eq}
{@eq value="underline"}{>underline/}{/eq}
{@eq value="strike_through"}{>strike_through/}{/eq}
{@eq value="code"}{>code/}{/eq}
{@eq value="verbatim"}{>verbatim/}{/eq}
{@eq value="plain_text"}{>plain_text/}{/eq}
{@eq value="regular_link"}{>regular_link/}{/eq}
{@eq value="radio_link"}{>radio_link/}{/eq}
{@eq value="radio_target"}{>radio_target/}{/eq}
{@eq value="plain_link"}{>plain_link/}{/eq}
{@eq value="angle_link"}{>angle_link/}{/eq}
{@eq value="org_macro"}{>org_macro/}{/eq}
{@eq value="entity"}{>entity/}{/eq}
{@eq value="latex_fragment"}{>latex_fragment/}{/eq}
{@eq value="export_snippet"}{>export_snippet/}{/eq}
{@eq value="footnote_reference"}{>footnote_reference/}{/eq}
{@eq value="citation"}{>citation/}{/eq}
{@eq value="citation_reference"}{>citation_reference/}{/eq}
{@eq value="inline_babel_call"}{>inline_babel_call/}{/eq}
{@eq value="inline_source_block"}{>inline_source_block/}{/eq}
{@eq value="line_break"}{>line_break/}{/eq}
{@eq value="target"}{>target/}{/eq}
{@eq value="statistics_cookie"}{>statistics_cookie/}{/eq}
{@eq value="subscript"}{>subscript/}{/eq}
{@eq value="superscript"}{>superscript/}{/eq}
{@eq value="timestamp"}{>timestamp/}{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized type {.type}.{/none}
{/select}{@gt key=.post_blank value=0}{~s}{/gt}

View File

@@ -0,0 +1 @@
!!!!!!!! org_macro

View File

@@ -0,0 +1,4 @@
<header class="page_header">
<a class="home_link" href="{.home_link}">{.website_title}</a>
{! TODO: Additional links? Probably using the nav semantic element. !}
</header>

View File

@@ -0,0 +1,3 @@
<p>{#.children}
{>object/}
{/.children}</p>

View File

@@ -0,0 +1 @@
!!!!!!!! plain_link

View File

@@ -0,0 +1,6 @@
{@select key=.list_type}
{@eq value="unordered"}<ul class="plain_list unordered">{#.children}{>plain_list_item/}{/.children}</ul>{/eq}
{@eq value="ordered"}<ol class="plain_list ordered">{#.children}{>plain_list_item/}{/.children}</ol>{/eq}
{@eq value="descriptive"}<dl class="plain_list descriptive">{#.children}{>plain_list_item/}{/.children}</dl>{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized list type {.list_type}.{/none}
{/select}

View File

@@ -0,0 +1,6 @@
{@select key=list_type}
{@eq value="unordered"}<li>{#.children}{>element/}{/.children}</li>{/eq}
{@eq value="ordered"}<li>{#.children}{>element/}{/.children}</li>{/eq}
{@eq value="descriptive"}<dt>{#.tag}{>object/}{/.tag}</dt><dd>{#.children}{>element/}{/.children}</dd>{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized list type {.list_type}.{/none}
{/select}

View File

@@ -0,0 +1,3 @@
{#.children}
{>object/}
{/.children}

View File

@@ -0,0 +1 @@
{.source}

View File

@@ -0,0 +1 @@
!!!!!!!! planning

View File

@@ -0,0 +1 @@
!!!!!!!! property_drawer

View File

@@ -0,0 +1,3 @@
<blockquote class="quote_block">{#.children}
{>element/}
{/.children}</blockquote>

View File

@@ -0,0 +1 @@
!!!!!!!! radio_link

View File

@@ -0,0 +1 @@
!!!!!!!! radio_target

View File

@@ -0,0 +1 @@
<div class="footnote_definition"><a id="{.definition_id}" href="{.reference_link}" class="label">{.label}.</a> <div class="definition">{#.contents}{>ast_node/}{/.contents}</div></div>

View File

@@ -0,0 +1 @@
<a href="{.raw_link}">{#.children}{>object/}{/.children}</a>

View File

@@ -0,0 +1,3 @@
{#.children}
{>element/}
{/.children}

View File

@@ -0,0 +1 @@
!!!!!!!! special_block

View File

@@ -0,0 +1,12 @@
<div class="src_block">
{?.language}<div class="src_language">{.language}</div>{/.language}
<table class="src_body">
<tbody>
{#.lines}
<tr>
<td><code class="src_line">{.}</code></td>
</tr>
{/.lines}
</tbody>
</table>
</div>

View File

@@ -0,0 +1 @@
!!!!!!!! statistics_cookie

View File

@@ -0,0 +1,3 @@
<del>{#.children}
{>object/}
{/.children}</del>

View File

@@ -0,0 +1 @@
!!!!!!!! subscript

View File

@@ -0,0 +1 @@
!!!!!!!! superscript

View File

@@ -0,0 +1 @@
<table>{#.children}{>table_row/}{/.children}</table>

View File

@@ -0,0 +1 @@
<td>{#.children}{>object/}{/.children}</td>

View File

@@ -0,0 +1 @@
<tr>{#.children}{>table_cell/}{/.children}</tr>

View File

@@ -0,0 +1 @@
<a id="{.id}"></a>

View File

@@ -0,0 +1 @@
!!!!!!!! timestamp

View File

@@ -0,0 +1,3 @@
<u>{#.children}
{>object/}
{/.children}</u>

View File

@@ -0,0 +1 @@
<code class="verbatim">{.contents}</code>

View File

@@ -0,0 +1 @@
!!!!!!!! verse_block

View File

@@ -0,0 +1,21 @@
# This test shows that footnote references only count if the definition containing them is rendered.
foo[fn:a:bar]
[fn:a] lorem
[fn:b] ipsum
[fn:d] fizz
[fn:c] dolar
yo[fn:b]
hello[fn:c]
[fn:e] buzz
sup[fn:d]

View File

@@ -0,0 +1,25 @@
# Test proves that:
#
# - Anonymous references with identical content get unique IDs.
# - Unreferenced footnote definitions are dropped.
# - Footnote definitions that come before their first reference are dropped.
foo[fn:2:something]
bar[fn::something]
baz[fn::something]
cat[fn::something]
dog[fn:3]
[fn:3] ipsum
[fn:4] lorem
[fn:3] dolar
[fn:5] not referenced
stuff[fn:4] and things

View File

@@ -0,0 +1,36 @@
* Double dollar
#+begin_src org
$$CONTENTS$$
#+end_src
becomes
#+begin_src text
\[CONTENTS\]
#+end_src
#+begin_src org
$$1+1=2$$
#+end_src
#+begin_src text
\[1+1=2\]
#+end_src
This gets interpreted by mathjax.
* Single Dollar
#+begin_src org
PRE$BORDER1 BODY BORDER2$POST
#+end_src
becomes
#+begin_src text
\(BORDER1 BODY BORDER2\)
#+end_src
#+begin_src org
foo$a bar b$.
#+end_src
becomes
#+begin_src text
foo\(a bar b\).
#+end_src
This gets interpreted by mathjax.

View File

@@ -4,7 +4,7 @@ use clap::Subcommand;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "Writer")] #[command(name = "Natter")]
#[command(version = env!("CARGO_PKG_VERSION"))] #[command(version = env!("CARGO_PKG_VERSION"))]
#[command(about = "Generate a static site.", long_about = None)] #[command(about = "Generate a static site.", long_about = None)]
#[command(propagate_version = true)] #[command(propagate_version = true)]
@@ -24,14 +24,14 @@ pub(crate) enum Commands {
#[derive(Args, Debug)] #[derive(Args, Debug)]
pub(crate) struct InitArgs { pub(crate) struct InitArgs {
/// Path where you want the initial writer structure to be located. /// Path where you want the initial natter structure to be located.
#[arg(short, long)] #[arg(short, long)]
pub(crate) path: PathBuf, pub(crate) path: PathBuf,
} }
#[derive(Args, Debug)] #[derive(Args, Debug)]
pub(crate) struct BuildArgs { pub(crate) struct BuildArgs {
/// Path to the writer config file. /// Path to the natter config file.
#[arg(short, long)] #[arg(short, long)]
pub(crate) config: PathBuf, pub(crate) config: PathBuf,
} }

View File

@@ -1,3 +1,5 @@
mod render;
mod runner; mod runner;
mod stylesheet;
pub(crate) use runner::build_site; pub(crate) use runner::build_site;

212
src/command/build/render.rs Normal file
View File

@@ -0,0 +1,212 @@
use std::ffi::OsStr;
use std::path::PathBuf;
use include_dir::include_dir;
use include_dir::Dir;
use crate::config::Config;
use crate::context::RenderBlogPostPage;
use crate::context::RenderBlogPostPageInput;
use crate::context::RenderBlogStream;
use crate::context::RenderBlogStreamInput;
use crate::context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost;
use crate::render::DusterRenderer;
use crate::render::RendererIntegration;
use super::stylesheet::Stylesheet;
static MAIN_TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/default_environment/templates/html");
pub(crate) struct SiteRenderer {
output_directory: PathBuf,
blog_posts: Vec<BlogPost>,
stylesheets: Vec<Stylesheet>,
}
impl SiteRenderer {
pub(crate) fn new<P: Into<PathBuf>>(
output_directory: P,
blog_posts: Vec<BlogPost>,
stylesheets: Vec<Stylesheet>,
) -> SiteRenderer {
SiteRenderer {
output_directory: output_directory.into(),
blog_posts,
stylesheets,
}
}
fn init_renderer_integration(&self) -> Result<DusterRenderer<'_>, CustomError> {
let mut renderer_integration = DusterRenderer::new();
let sources: Vec<_> = MAIN_TEMPLATES
.files()
.filter(|f| f.path().extension() == Some(OsStr::new("dust")))
.collect();
if sources
.iter()
.filter(|f| f.path().file_stem() == Some(OsStr::new("main")))
.count()
!= 1
{
return Err("Expect exactly 1 main.dust template file.".into());
}
let decoded_templates = {
let mut decoded_templates = Vec::with_capacity(sources.len());
for entry in sources {
decoded_templates.push(build_name_contents_pairs(entry)?);
}
decoded_templates
};
for (name, contents) in decoded_templates {
renderer_integration.load_template(name, contents)?;
}
Ok(renderer_integration)
}
pub(crate) async fn render_blog_posts(&self, config: &Config) -> Result<(), CustomError> {
let renderer_integration = self.init_renderer_integration()?;
for blog_post in &self.blog_posts {
for blog_post_page in &blog_post.pages {
let output_path = self
.output_directory
.join(config.get_relative_path_to_post(&blog_post.id))
.join(blog_post_page.get_output_path());
let convert_input = RenderBlogPostPageInput::new(blog_post, blog_post_page);
let render_context = RenderContext::new(
config,
self.output_directory.as_path(),
output_path.as_path(),
None,
)?;
let render_context = RenderBlogPostPage::new(render_context, &convert_input)?;
let rendered_output = renderer_integration.render(render_context)?;
let parent_directory = output_path
.parent()
.ok_or("Output file should have a containing directory.")?;
tokio::fs::create_dir_all(parent_directory).await?;
tokio::fs::write(output_path, rendered_output).await?;
}
}
Ok(())
}
pub(crate) async fn render_blog_stream(&self, config: &Config) -> Result<(), CustomError> {
let renderer_integration = self.init_renderer_integration()?;
// Sort blog posts by date, newest first.
let sorted_blog_posts = {
let mut sorted_blog_posts: Vec<_> = self.blog_posts.iter().collect();
sorted_blog_posts
.sort_by_key(|blog_post| (blog_post.get_date(), blog_post.id.as_str()));
sorted_blog_posts.reverse();
sorted_blog_posts
};
for blog_post in &sorted_blog_posts {
if blog_post.get_date().is_none() {
return Err(format!("Blog post {} does not have a date.", blog_post.id).into());
}
}
// Group blog posts based on # of posts per page.
let stream_chunks: Vec<_> = sorted_blog_posts
.chunks(config.get_stream_entries_per_page())
.collect();
// For each group, create a RenderBlogStream.
let num_stream_pages = stream_chunks.len();
for (page_num, chunk) in stream_chunks.into_iter().enumerate() {
let output_file = if page_num == 0 {
self.output_directory.join("index.html")
} else {
self.output_directory
.join("stream")
.join(format!("{}.html", page_num))
};
let newer_link = if page_num == 0 {
None
} else if page_num == 1 {
Some(get_web_path(
config,
&self.output_directory,
&output_file,
"index.html",
)?)
} else {
Some(get_web_path(
config,
&self.output_directory,
&output_file,
format!("stream/{}.html", page_num - 1),
)?)
};
let older_link = if page_num == (num_stream_pages - 1) {
None
} else {
Some(get_web_path(
config,
&self.output_directory,
&output_file,
format!("stream/{}.html", page_num + 1),
)?)
};
let convert_input = RenderBlogStreamInput::new(chunk, older_link, newer_link);
let render_context = RenderContext::new(
config,
self.output_directory.as_path(),
output_file.as_path(),
None,
)?;
let blog_stream = RenderBlogStream::new(render_context, &convert_input)?;
// Pass each RenderBlogStream to dust as the context to render index.html and any additional stream pages.
let rendered_output = renderer_integration.render(blog_stream)?;
let parent_directory = output_file
.parent()
.ok_or("Output file should have a containing directory.")?;
tokio::fs::create_dir_all(parent_directory).await?;
tokio::fs::write(output_file, rendered_output).await?;
}
Ok(())
}
pub(crate) async fn render_stylesheets(&self) -> Result<(), CustomError> {
let stylesheet_output_directory = self.output_directory.join("stylesheet");
if !stylesheet_output_directory.exists() {
tokio::fs::create_dir(&stylesheet_output_directory).await?;
}
for stylesheet in &self.stylesheets {
let file_output_path = stylesheet_output_directory.join(&stylesheet.path);
let parent_directory = file_output_path
.parent()
.ok_or("Output file should have a containing directory.")?;
tokio::fs::create_dir_all(parent_directory).await?;
tokio::fs::write(file_output_path, stylesheet.contents.as_bytes()).await?;
}
Ok(())
}
}
fn build_name_contents_pairs<'a>(
entry: &'a include_dir::File<'_>,
) -> Result<(&'a str, &'a str), CustomError> {
let path = entry.path();
let name = path
.file_stem()
.ok_or("All templates should have a stem.")?
.to_str()
.ok_or("All template filenames should be valid utf-8.")?;
let contents = std::str::from_utf8(entry.contents())?;
Ok((name, contents))
}

View File

@@ -1,7 +1,89 @@
use crate::cli::parameters::BuildArgs; use std::ffi::OsStr;
use crate::config::Config; use std::path::PathBuf;
use super::stylesheet::Stylesheet;
use crate::cli::parameters::BuildArgs;
use crate::command::build::render::SiteRenderer;
use crate::config::Config;
use crate::error::CustomError;
use crate::intermediate::BlogPost;
use include_dir::include_dir;
use include_dir::Dir;
static DEFAULT_STYLESHEETS: Dir =
include_dir!("$CARGO_MANIFEST_DIR/default_environment/stylesheet");
pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> {
let config = Config::load_from_file(args.config).await?;
let blog_posts = load_blog_posts(&config).await?;
let stylesheets = load_stylesheets().await?;
let renderer = SiteRenderer::new(
get_output_directory(&config).await?,
blog_posts,
stylesheets,
);
renderer.render_blog_posts(&config).await?;
renderer.render_blog_stream(&config).await?;
renderer.render_stylesheets().await?;
pub(crate) async fn build_site(args: BuildArgs) -> Result<(), Box<dyn std::error::Error>> {
let _config = Config::load_from_file(args.config).await?;
Ok(()) Ok(())
} }
/// Delete everything inside the output directory and return the path to that directory.
async fn get_output_directory(config: &Config) -> Result<PathBuf, CustomError> {
let output_directory = config.get_output_directory();
if !output_directory.exists() {
tokio::fs::create_dir(&output_directory).await?;
} else {
let mut existing_entries = tokio::fs::read_dir(&output_directory).await?;
while let Some(entry) = existing_entries.next_entry().await? {
let file_type = entry.file_type().await?;
if file_type.is_dir() {
tokio::fs::remove_dir_all(entry.path()).await?;
} else {
tokio::fs::remove_file(entry.path()).await?;
}
}
}
Ok(output_directory)
}
async fn get_post_directories(config: &Config) -> Result<Vec<PathBuf>, CustomError> {
let mut ret = Vec::new();
let mut entries = tokio::fs::read_dir(config.get_posts_directory()).await?;
while let Some(entry) = entries.next_entry().await? {
let file_type = entry.file_type().await?;
if file_type.is_dir() {
ret.push(entry.path());
}
}
Ok(ret)
}
async fn load_blog_posts(config: &Config) -> Result<Vec<BlogPost>, CustomError> {
let root_directory = config.get_root_directory().to_owned();
let post_directories = get_post_directories(&config).await?;
let load_jobs = post_directories
.into_iter()
.map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path)));
let mut blog_posts = Vec::new();
for job in load_jobs {
blog_posts.push(job.await??);
}
Ok(blog_posts)
}
async fn load_stylesheets() -> Result<Vec<Stylesheet>, CustomError> {
let sources: Vec<_> = DEFAULT_STYLESHEETS
.files()
.filter(|f| f.path().extension() == Some(OsStr::new("css")))
.collect();
let mut ret = Vec::with_capacity(sources.len());
for entry in sources {
let path = entry.path().to_path_buf();
let contents = String::from_utf8(entry.contents().to_vec())?;
let stylesheet = Stylesheet::new(path, contents).await?;
ret.push(stylesheet);
}
Ok(ret)
}

View File

@@ -0,0 +1,15 @@
use std::path::PathBuf;
use crate::error::CustomError;
#[derive(Debug)]
pub(crate) struct Stylesheet {
pub(crate) path: PathBuf,
pub(crate) contents: String,
}
impl Stylesheet {
pub(crate) async fn new(path: PathBuf, contents: String) -> Result<Stylesheet, CustomError> {
Ok(Stylesheet { path, contents })
}
}

View File

@@ -1,3 +1,3 @@
mod runner; mod runner;
pub(crate) use runner::init_writer_folder; pub(crate) use runner::init_natter_folder;

View File

@@ -1,7 +1,8 @@
use crate::cli::parameters::InitArgs; use crate::cli::parameters::InitArgs;
use crate::config::Config; use crate::config::Config;
use crate::error::CustomError;
pub(crate) async fn init_writer_folder(args: InitArgs) -> Result<(), Box<dyn std::error::Error>> { pub(crate) async fn init_natter_folder(args: InitArgs) -> Result<(), CustomError> {
if args.path.exists() && !args.path.is_dir() { if args.path.exists() && !args.path.is_dir() {
return Err("The supplied path exists but is not a directory. Aborting.".into()); return Err("The supplied path exists but is not a directory. Aborting.".into());
} }

View File

@@ -3,18 +3,21 @@ use std::path::PathBuf;
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use crate::error::CustomError;
use super::raw::RawConfig; use super::raw::RawConfig;
/// This is the config struct used by most of the code, which is an interpreted version of the RawConfig struct which is the raw disk-representation of the config. /// This is the config struct used by most of the code, which is an interpreted version of the RawConfig struct which is the raw disk-representation of the config.
#[derive(Debug)]
pub(crate) struct Config { pub(crate) struct Config {
raw: RawConfig, raw: RawConfig,
config_path: PathBuf, config_path: PathBuf,
} }
impl Config { impl Config {
pub(crate) fn new<P: AsRef<Path>>(root_dir: P) -> Result<Config, Box<dyn std::error::Error>> { pub(crate) fn new<P: AsRef<Path>>(root_dir: P) -> Result<Config, CustomError> {
fn inner(root_dir: &Path) -> Result<Config, Box<dyn std::error::Error>> { fn inner(root_dir: &Path) -> Result<Config, CustomError> {
let file_path = root_dir.join("writer.toml"); let file_path = root_dir.join("natter.toml");
Ok(Config { Ok(Config {
raw: RawConfig::default(), raw: RawConfig::default(),
config_path: file_path, config_path: file_path,
@@ -23,10 +26,8 @@ impl Config {
inner(root_dir.as_ref()) inner(root_dir.as_ref())
} }
pub(crate) async fn load_from_file<P: Into<PathBuf>>( pub(crate) async fn load_from_file<P: Into<PathBuf>>(path: P) -> Result<Config, CustomError> {
path: P, async fn inner(path: PathBuf) -> Result<Config, CustomError> {
) -> Result<Config, Box<dyn std::error::Error>> {
async fn inner(path: PathBuf) -> Result<Config, Box<dyn std::error::Error>> {
let contents = tokio::fs::read_to_string(&path).await?; let contents = tokio::fs::read_to_string(&path).await?;
let parsed_contents: RawConfig = toml::from_str(contents.as_str())?; let parsed_contents: RawConfig = toml::from_str(contents.as_str())?;
Ok(Config { Ok(Config {
@@ -37,11 +38,56 @@ impl Config {
inner(path.into()).await inner(path.into()).await
} }
pub(crate) async fn write_to_disk(&self) -> Result<(), Box<dyn std::error::Error>> { pub(crate) async fn write_to_disk(&self) -> Result<(), CustomError> {
let mut config_file = File::create(&self.config_path).await?; let mut config_file = File::create(&self.config_path).await?;
config_file config_file
.write_all(toml::to_string(&self.raw)?.as_bytes()) .write_all(toml::to_string(&self.raw)?.as_bytes())
.await?; .await?;
Ok(()) Ok(())
} }
pub(crate) fn get_root_directory(&self) -> &Path {
&self
.config_path
.parent()
.expect("Config file must exist inside a directory.")
}
pub(crate) fn get_posts_directory(&self) -> PathBuf {
self.get_root_directory().join("posts")
}
/// Get the relative path to the folder containing a blog post.
///
/// This could be appended to the output root directory to get the
/// blog post output folder or it could be used to generate a link
/// to the blog post.
pub(crate) fn get_relative_path_to_post<P: AsRef<Path>>(&self, post_id: P) -> PathBuf {
Path::new("posts").join(post_id)
}
pub(crate) fn get_output_directory(&self) -> PathBuf {
self.get_root_directory().join("output")
}
pub(crate) fn use_relative_paths(&self) -> bool {
self.raw.use_relative_paths.unwrap_or(true)
}
pub(crate) fn get_web_root(&self) -> Option<&str> {
self.raw.web_root.as_deref()
}
pub(crate) fn get_site_title(&self) -> Option<&str> {
self.raw.site_title.as_deref()
}
pub(crate) fn get_stream_entries_per_page(&self) -> usize {
self.raw
.stream
.as_ref()
.map(|stream| stream.entries_per_page)
.flatten()
.unwrap_or(5)
}
} }

View File

@@ -1,20 +1,39 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
/// This is the struct for the writer.toml config file that ends up in each site's root directory. /// This is the struct for the natter.toml config file that ends up in each site's root directory.
#[derive(Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub(crate) struct RawConfig { pub(crate) struct RawConfig {
site_title: String, pub(super) site_title: Option<String>,
author: Option<String>, author: Option<String>,
email: Option<String>, email: Option<String>,
pub(super) use_relative_paths: Option<bool>,
pub(super) web_root: Option<String>,
pub(super) stream: Option<RawConfigStream>,
} }
impl Default for RawConfig { impl Default for RawConfig {
fn default() -> Self { fn default() -> Self {
RawConfig { RawConfig {
site_title: "My super awesome website".to_owned(), site_title: None,
author: None, author: None,
email: None, email: None,
use_relative_paths: None,
web_root: None,
stream: None,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct RawConfigStream {
pub(super) entries_per_page: Option<usize>,
}
impl Default for RawConfigStream {
fn default() -> Self {
RawConfigStream {
entries_per_page: None,
} }
} }
} }

16
src/context/angle_link.rs Normal file
View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::IAngleLink;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "angle_link")]
pub(crate) struct RenderAngleLink {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderAngleLink, IAngleLink);

327
src/context/ast_node.rs Normal file
View File

@@ -0,0 +1,327 @@
use serde::Serialize;
use crate::error::CustomError;
use crate::intermediate::IAstNode;
use super::angle_link::RenderAngleLink;
use super::babel_call::RenderBabelCall;
use super::bold::RenderBold;
use super::center_block::RenderCenterBlock;
use super::citation::RenderCitation;
use super::citation_reference::RenderCitationReference;
use super::clock::RenderClock;
use super::code::RenderCode;
use super::comment::RenderComment;
use super::comment_block::RenderCommentBlock;
use super::diary_sexp::RenderDiarySexp;
use super::drawer::RenderDrawer;
use super::dynamic_block::RenderDynamicBlock;
use super::entity::RenderEntity;
use super::example_block::RenderExampleBlock;
use super::export_block::RenderExportBlock;
use super::export_snippet::RenderExportSnippet;
use super::fixed_width_area::RenderFixedWidthArea;
use super::footnote_definition::RenderFootnoteDefinition;
use super::footnote_reference::RenderFootnoteReference;
use super::horizontal_rule::RenderHorizontalRule;
use super::inline_babel_call::RenderInlineBabelCall;
use super::inline_source_block::RenderInlineSourceBlock;
use super::italic::RenderItalic;
use super::keyword::RenderKeyword;
use super::latex_environment::RenderLatexEnvironment;
use super::latex_fragment::RenderLatexFragment;
use super::line_break::RenderLineBreak;
use super::org_macro::RenderOrgMacro;
use super::paragraph::RenderParagraph;
use super::plain_link::RenderPlainLink;
use super::plain_list::RenderPlainList;
use super::plain_text::RenderPlainText;
use super::planning::RenderPlanning;
use super::property_drawer::RenderPropertyDrawer;
use super::quote_block::RenderQuoteBlock;
use super::radio_link::RenderRadioLink;
use super::radio_target::RenderRadioTarget;
use super::regular_link::RenderRegularLink;
use super::render_context::RenderContext;
use super::special_block::RenderSpecialBlock;
use super::src_block::RenderSrcBlock;
use super::statistics_cookie::RenderStatisticsCookie;
use super::strike_through::RenderStrikeThrough;
use super::subscript::RenderSubscript;
use super::superscript::RenderSuperscript;
use super::table::RenderTable;
use super::target::RenderTarget;
use super::timestamp::RenderTimestamp;
use super::underline::RenderUnderline;
use super::verbatim::RenderVerbatim;
use super::verse_block::RenderVerseBlock;
use super::RenderHeading;
use super::RenderSection;
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub(crate) enum RenderAstNode {
Heading(RenderHeading),
Section(RenderSection),
Paragraph(RenderParagraph),
PlainList(RenderPlainList),
CenterBlock(RenderCenterBlock),
QuoteBlock(RenderQuoteBlock),
SpecialBlock(RenderSpecialBlock),
DynamicBlock(RenderDynamicBlock),
FootnoteDefinition(RenderFootnoteDefinition),
Comment(RenderComment),
Drawer(RenderDrawer),
PropertyDrawer(RenderPropertyDrawer),
Table(RenderTable),
VerseBlock(RenderVerseBlock),
CommentBlock(RenderCommentBlock),
ExampleBlock(RenderExampleBlock),
ExportBlock(RenderExportBlock),
SrcBlock(RenderSrcBlock),
Clock(RenderClock),
DiarySexp(RenderDiarySexp),
Planning(RenderPlanning),
FixedWidthArea(RenderFixedWidthArea),
HorizontalRule(RenderHorizontalRule),
Keyword(RenderKeyword),
BabelCall(RenderBabelCall),
LatexEnvironment(RenderLatexEnvironment),
Bold(RenderBold),
Italic(RenderItalic),
Underline(RenderUnderline),
StrikeThrough(RenderStrikeThrough),
Code(RenderCode),
Verbatim(RenderVerbatim),
PlainText(RenderPlainText),
RegularLink(RenderRegularLink),
RadioLink(RenderRadioLink),
RadioTarget(RenderRadioTarget),
PlainLink(RenderPlainLink),
AngleLink(RenderAngleLink),
OrgMacro(RenderOrgMacro),
Entity(RenderEntity),
LatexFragment(RenderLatexFragment),
ExportSnippet(RenderExportSnippet),
FootnoteReference(RenderFootnoteReference),
Citation(RenderCitation),
CitationReference(RenderCitationReference),
InlineBabelCall(RenderInlineBabelCall),
InlineSourceBlock(RenderInlineSourceBlock),
LineBreak(RenderLineBreak),
Target(RenderTarget),
StatisticsCookie(RenderStatisticsCookie),
Subscript(RenderSubscript),
Superscript(RenderSuperscript),
Timestamp(RenderTimestamp),
}
pub(crate) trait IntoRenderAstNode {
fn into_render_ast_node(
&self,
render_context: RenderContext<'_>,
) -> Result<RenderAstNode, CustomError>;
}
impl IntoRenderAstNode for IAstNode {
fn into_render_ast_node(
&self,
render_context: RenderContext<'_>,
) -> Result<RenderAstNode, CustomError> {
match self {
IAstNode::Heading(inner) => Ok(RenderAstNode::Heading(RenderHeading::new(
render_context,
inner,
)?)),
IAstNode::Section(inner) => Ok(RenderAstNode::Section(RenderSection::new(
render_context,
inner,
)?)),
IAstNode::Paragraph(inner) => Ok(RenderAstNode::Paragraph(RenderParagraph::new(
render_context,
inner,
)?)),
IAstNode::PlainList(inner) => Ok(RenderAstNode::PlainList(RenderPlainList::new(
render_context,
inner,
)?)),
IAstNode::CenterBlock(inner) => Ok(RenderAstNode::CenterBlock(RenderCenterBlock::new(
render_context,
inner,
)?)),
IAstNode::QuoteBlock(inner) => Ok(RenderAstNode::QuoteBlock(RenderQuoteBlock::new(
render_context,
inner,
)?)),
IAstNode::SpecialBlock(inner) => Ok(RenderAstNode::SpecialBlock(
RenderSpecialBlock::new(render_context, inner)?,
)),
IAstNode::DynamicBlock(inner) => Ok(RenderAstNode::DynamicBlock(
RenderDynamicBlock::new(render_context, inner)?,
)),
IAstNode::FootnoteDefinition(inner) => Ok(RenderAstNode::FootnoteDefinition(
RenderFootnoteDefinition::new(render_context, inner)?,
)),
IAstNode::Comment(inner) => Ok(RenderAstNode::Comment(RenderComment::new(
render_context,
inner,
)?)),
IAstNode::Drawer(inner) => Ok(RenderAstNode::Drawer(RenderDrawer::new(
render_context,
inner,
)?)),
IAstNode::PropertyDrawer(inner) => Ok(RenderAstNode::PropertyDrawer(
RenderPropertyDrawer::new(render_context, inner)?,
)),
IAstNode::Table(inner) => Ok(RenderAstNode::Table(RenderTable::new(
render_context,
inner,
)?)),
IAstNode::VerseBlock(inner) => Ok(RenderAstNode::VerseBlock(RenderVerseBlock::new(
render_context,
inner,
)?)),
IAstNode::CommentBlock(inner) => Ok(RenderAstNode::CommentBlock(
RenderCommentBlock::new(render_context, inner)?,
)),
IAstNode::ExampleBlock(inner) => Ok(RenderAstNode::ExampleBlock(
RenderExampleBlock::new(render_context, inner)?,
)),
IAstNode::ExportBlock(inner) => Ok(RenderAstNode::ExportBlock(RenderExportBlock::new(
render_context,
inner,
)?)),
IAstNode::SrcBlock(inner) => Ok(RenderAstNode::SrcBlock(RenderSrcBlock::new(
render_context,
inner,
)?)),
IAstNode::Clock(inner) => Ok(RenderAstNode::Clock(RenderClock::new(
render_context,
inner,
)?)),
IAstNode::DiarySexp(inner) => Ok(RenderAstNode::DiarySexp(RenderDiarySexp::new(
render_context,
inner,
)?)),
IAstNode::Planning(inner) => Ok(RenderAstNode::Planning(RenderPlanning::new(
render_context,
inner,
)?)),
IAstNode::FixedWidthArea(inner) => Ok(RenderAstNode::FixedWidthArea(
RenderFixedWidthArea::new(render_context, inner)?,
)),
IAstNode::HorizontalRule(inner) => Ok(RenderAstNode::HorizontalRule(
RenderHorizontalRule::new(render_context, inner)?,
)),
IAstNode::Keyword(inner) => Ok(RenderAstNode::Keyword(RenderKeyword::new(
render_context,
inner,
)?)),
IAstNode::BabelCall(inner) => Ok(RenderAstNode::BabelCall(RenderBabelCall::new(
render_context,
inner,
)?)),
IAstNode::LatexEnvironment(inner) => Ok(RenderAstNode::LatexEnvironment(
RenderLatexEnvironment::new(render_context, inner)?,
)),
IAstNode::Bold(inner) => {
Ok(RenderAstNode::Bold(RenderBold::new(render_context, inner)?))
}
IAstNode::Italic(inner) => Ok(RenderAstNode::Italic(RenderItalic::new(
render_context,
inner,
)?)),
IAstNode::Underline(inner) => Ok(RenderAstNode::Underline(RenderUnderline::new(
render_context,
inner,
)?)),
IAstNode::StrikeThrough(inner) => Ok(RenderAstNode::StrikeThrough(
RenderStrikeThrough::new(render_context, inner)?,
)),
IAstNode::Code(inner) => {
Ok(RenderAstNode::Code(RenderCode::new(render_context, inner)?))
}
IAstNode::Verbatim(inner) => Ok(RenderAstNode::Verbatim(RenderVerbatim::new(
render_context,
inner,
)?)),
IAstNode::PlainText(inner) => Ok(RenderAstNode::PlainText(RenderPlainText::new(
render_context,
inner,
)?)),
IAstNode::RegularLink(inner) => Ok(RenderAstNode::RegularLink(RenderRegularLink::new(
render_context,
inner,
)?)),
IAstNode::RadioLink(inner) => Ok(RenderAstNode::RadioLink(RenderRadioLink::new(
render_context,
inner,
)?)),
IAstNode::RadioTarget(inner) => Ok(RenderAstNode::RadioTarget(RenderRadioTarget::new(
render_context,
inner,
)?)),
IAstNode::PlainLink(inner) => Ok(RenderAstNode::PlainLink(RenderPlainLink::new(
render_context,
inner,
)?)),
IAstNode::AngleLink(inner) => Ok(RenderAstNode::AngleLink(RenderAngleLink::new(
render_context,
inner,
)?)),
IAstNode::OrgMacro(inner) => Ok(RenderAstNode::OrgMacro(RenderOrgMacro::new(
render_context,
inner,
)?)),
IAstNode::Entity(inner) => Ok(RenderAstNode::Entity(RenderEntity::new(
render_context,
inner,
)?)),
IAstNode::LatexFragment(inner) => Ok(RenderAstNode::LatexFragment(
RenderLatexFragment::new(render_context, inner)?,
)),
IAstNode::ExportSnippet(inner) => Ok(RenderAstNode::ExportSnippet(
RenderExportSnippet::new(render_context, inner)?,
)),
IAstNode::FootnoteReference(inner) => Ok(RenderAstNode::FootnoteReference(
RenderFootnoteReference::new(render_context, inner)?,
)),
IAstNode::Citation(inner) => Ok(RenderAstNode::Citation(RenderCitation::new(
render_context,
inner,
)?)),
IAstNode::CitationReference(inner) => Ok(RenderAstNode::CitationReference(
RenderCitationReference::new(render_context, inner)?,
)),
IAstNode::InlineBabelCall(inner) => Ok(RenderAstNode::InlineBabelCall(
RenderInlineBabelCall::new(render_context, inner)?,
)),
IAstNode::InlineSourceBlock(inner) => Ok(RenderAstNode::InlineSourceBlock(
RenderInlineSourceBlock::new(render_context, inner)?,
)),
IAstNode::LineBreak(inner) => Ok(RenderAstNode::LineBreak(RenderLineBreak::new(
render_context,
inner,
)?)),
IAstNode::Target(inner) => Ok(RenderAstNode::Target(RenderTarget::new(
render_context,
inner,
)?)),
IAstNode::StatisticsCookie(inner) => Ok(RenderAstNode::StatisticsCookie(
RenderStatisticsCookie::new(render_context, inner)?,
)),
IAstNode::Subscript(inner) => Ok(RenderAstNode::Subscript(RenderSubscript::new(
render_context,
inner,
)?)),
IAstNode::Superscript(inner) => Ok(RenderAstNode::Superscript(RenderSuperscript::new(
render_context,
inner,
)?)),
IAstNode::Timestamp(inner) => Ok(RenderAstNode::Timestamp(RenderTimestamp::new(
render_context,
inner,
)?)),
}
}
}

16
src/context/babel_call.rs Normal file
View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::IBabelCall;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "babel_call")]
pub(crate) struct RenderBabelCall {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderBabelCall, IBabelCall);

View File

@@ -0,0 +1,123 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost;
use crate::intermediate::BlogPostPage;
use super::footnote_definition::RenderRealFootnoteDefinition;
use super::macros::render;
use super::GlobalSettings;
use super::PageHeader;
use super::RenderDocumentElement;
#[derive(Debug)]
pub(crate) struct RenderBlogPostPageInput<'a> {
post: &'a BlogPost,
page: &'a BlogPostPage,
}
impl<'a> RenderBlogPostPageInput<'a> {
pub(crate) fn new(post: &'a BlogPost, page: &'a BlogPostPage) -> RenderBlogPostPageInput<'a> {
RenderBlogPostPageInput { post, page }
}
}
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "blog_post_page")]
pub(crate) struct RenderBlogPostPage {
global_settings: GlobalSettings,
page_header: Option<PageHeader>,
/// The title that will be shown visibly on the page.
title: Option<String>,
self_link: Option<String>,
children: Vec<RenderDocumentElement>,
footnotes: Vec<RenderRealFootnoteDefinition>,
}
render!(
RenderBlogPostPage,
RenderBlogPostPageInput,
original,
render_context,
{
let css_files = vec![
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"stylesheet/reset.css",
)?,
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"stylesheet/main.css",
)?,
];
let js_files = vec![get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"blog_post.js",
)?];
let global_settings = GlobalSettings::new(original.page.title.clone(), css_files, js_files);
let page_header = PageHeader::new(
render_context.config.get_site_title().map(str::to_string),
Some(get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"",
)?),
);
let link_to_blog_post = get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
render_context
.output_file
.strip_prefix(render_context.output_root_directory)?,
)?;
let children = {
let mut children = Vec::new();
for child in original.page.children.iter() {
children.push(RenderDocumentElement::new(render_context.clone(), child)?);
}
children
};
let footnotes = {
let mut ret = Vec::new();
for footnote in original.page.footnotes.iter() {
ret.push(RenderRealFootnoteDefinition::new(
render_context.clone(),
footnote,
)?);
}
ret
};
let ret = RenderBlogPostPage {
global_settings,
page_header: Some(page_header),
title: original.page.title.clone(),
self_link: Some(link_to_blog_post),
children,
footnotes,
};
Ok(ret)
}
);

213
src/context/blog_stream.rs Normal file
View File

@@ -0,0 +1,213 @@
use serde::Serialize;
use super::macros::render;
use super::render_context::RenderContext;
use crate::context::RenderDocumentElement;
use crate::context::RenderRealFootnoteDefinition;
use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost;
use super::GlobalSettings;
use super::PageHeader;
#[derive(Debug)]
pub(crate) struct RenderBlogStreamInput<'a, 'b> {
original: &'a [&'b BlogPost],
older_link: Option<String>,
newer_link: Option<String>,
}
impl<'a, 'b> RenderBlogStreamInput<'a, 'b> {
pub(crate) fn new(
original: &'a [&'b BlogPost],
older_link: Option<String>,
newer_link: Option<String>,
) -> RenderBlogStreamInput<'a, 'b> {
RenderBlogStreamInput {
original,
older_link,
newer_link,
}
}
}
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "blog_stream")]
pub(crate) struct RenderBlogStream {
global_settings: GlobalSettings,
page_header: Option<PageHeader>,
children: Vec<RenderBlogStreamEntry>,
stream_pagination: Option<RenderBlogStreamPagination>,
}
render!(
RenderBlogStream,
RenderBlogStreamInput,
original,
render_context,
{
let css_files = vec![
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"stylesheet/reset.css",
)?,
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"stylesheet/main.css",
)?,
];
let js_files = vec![get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"blog_post.js",
)?];
let global_settings = GlobalSettings::new(
render_context.config.get_site_title().map(str::to_string),
css_files,
js_files,
);
let page_header = PageHeader::new(
render_context.config.get_site_title().map(str::to_string),
Some(get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"",
)?),
);
let children = original
.original
.into_iter()
.enumerate()
.map(|(i, blog_post)| {
RenderBlogStreamEntry::new(
render_context.clone(),
&RenderBlogStreamEntryInput::new(blog_post, i),
)
})
.collect::<Result<Vec<_>, _>>()?;
let stream_pagination = if original.older_link.is_some() || original.newer_link.is_some() {
Some(RenderBlogStreamPagination::new(
original.older_link.clone(),
original.newer_link.clone(),
)?)
} else {
None
};
Ok(RenderBlogStream {
global_settings,
page_header: Some(page_header),
children,
stream_pagination,
})
}
);
#[derive(Debug)]
pub(crate) struct RenderBlogStreamEntryInput<'a> {
original: &'a BlogPost,
offset: usize,
}
impl<'a> RenderBlogStreamEntryInput<'a> {
fn new(original: &'a BlogPost, offset: usize) -> RenderBlogStreamEntryInput<'a> {
RenderBlogStreamEntryInput { original, offset }
}
}
#[derive(Debug, Serialize)]
pub(crate) struct RenderBlogStreamEntry {
/// The title that will be shown visibly on the page.
title: Option<String>,
self_link: Option<String>,
children: Vec<RenderDocumentElement>,
footnotes: Vec<RenderRealFootnoteDefinition>,
}
render!(
RenderBlogStreamEntry,
RenderBlogStreamEntryInput,
original,
render_context,
{
let offset_string = original.offset.to_string();
let render_context = {
let mut render_context = render_context.clone();
render_context.id_addition = Some(offset_string.as_str());
render_context
};
let link_to_blog_post = get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
render_context
.config
.get_relative_path_to_post(&original.original.id),
)?;
// TODO: Should I guess an index page instead of erroring out?
let index_page = original
.original
.get_index_page()
.ok_or_else(|| format!("Blog post {} needs an index page.", original.original.id))?;
let title = index_page.title.clone();
let children = index_page
.children
.iter()
.map(|child| RenderDocumentElement::new(render_context.clone(), child))
.collect::<Result<Vec<_>, _>>()?;
let footnotes = {
let mut ret = Vec::new();
for footnote in index_page.footnotes.iter() {
ret.push(RenderRealFootnoteDefinition::new(
render_context.clone(),
footnote,
)?);
}
ret
};
Ok(RenderBlogStreamEntry {
title,
self_link: Some(link_to_blog_post),
children,
footnotes,
})
}
);
#[derive(Debug, Serialize)]
pub(crate) struct RenderBlogStreamPagination {
older_link: Option<String>,
newer_link: Option<String>,
}
impl RenderBlogStreamPagination {
fn new(
older_link: Option<String>,
newer_link: Option<String>,
) -> Result<RenderBlogStreamPagination, CustomError> {
Ok(RenderBlogStreamPagination {
older_link,
newer_link,
})
}
}

31
src/context/bold.rs Normal file
View File

@@ -0,0 +1,31 @@
use serde::Serialize;
use crate::error::CustomError;
use crate::intermediate::IBold;
use super::macros::render;
use super::render_context::RenderContext;
use super::RenderObject;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "bold")]
pub(crate) struct RenderBold {
children: Vec<RenderObject>,
post_blank: organic::types::PostBlank,
}
render!(RenderBold, IBold, original, render_context, {
let children = {
let mut ret = Vec::new();
for obj in original.children.iter() {
ret.push(RenderObject::new(render_context.clone(), obj)?);
}
ret
};
Ok(RenderBold {
children,
post_blank: original.post_blank,
})
});

View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::ICenterBlock;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "center_block")]
pub(crate) struct RenderCenterBlock {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderCenterBlock, ICenterBlock);

16
src/context/citation.rs Normal file
View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::ICitation;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "citation")]
pub(crate) struct RenderCitation {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderCitation, ICitation);

View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::ICitationReference;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "citation_reference")]
pub(crate) struct RenderCitationReference {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderCitationReference, ICitationReference);

16
src/context/clock.rs Normal file
View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::IClock;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "clock")]
pub(crate) struct RenderClock {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderClock, IClock);

22
src/context/code.rs Normal file
View File

@@ -0,0 +1,22 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::ICode;
use super::macros::render;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "code")]
pub(crate) struct RenderCode {
contents: String,
post_blank: organic::types::PostBlank,
}
render!(RenderCode, ICode, original, _render_context, {
Ok(RenderCode {
contents: original.contents.clone(),
post_blank: original.post_blank,
})
});

16
src/context/comment.rs Normal file
View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::IComment;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "comment")]
pub(crate) struct RenderComment {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderComment, IComment);

View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::ICommentBlock;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "comment_block")]
pub(crate) struct RenderCommentBlock {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderCommentBlock, ICommentBlock);

16
src/context/diary_sexp.rs Normal file
View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::IDiarySexp;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "diary_sexp")]
pub(crate) struct RenderDiarySexp {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderDiarySexp, IDiarySexp);

View File

@@ -0,0 +1,33 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::IDocumentElement;
use super::macros::render;
use super::RenderHeading;
use super::RenderSection;
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub(crate) enum RenderDocumentElement {
Heading(RenderHeading),
Section(RenderSection),
}
render!(
RenderDocumentElement,
IDocumentElement,
original,
render_context,
{
match original {
IDocumentElement::Heading(inner) => Ok(RenderDocumentElement::Heading(
RenderHeading::new(render_context.clone(), inner)?,
)),
IDocumentElement::Section(inner) => Ok(RenderDocumentElement::Section(
RenderSection::new(render_context.clone(), inner)?,
)),
}
}
);

16
src/context/drawer.rs Normal file
View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use super::render_context::RenderContext;
use crate::error::CustomError;
use crate::intermediate::IDrawer;
use super::macros::rnoop;
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "drawer")]
pub(crate) struct RenderDrawer {
post_blank: organic::types::PostBlank,
}
rnoop!(RenderDrawer, IDrawer);

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