Compare commits

...

52 Commits

Author SHA1 Message Date
fluxcdbot
b3929f22f3 CI: autofix rust code.
Some checks failed
clippy Build clippy has failed
format Build format has succeeded
rust-test Build rust-test has failed
build Build build has succeeded
2025-08-31 22:24:16 +00:00
Tom Alexander
bad12160ac Let chains have been stabalized.
Some checks are pending
clippy Build clippy has started
format Build format has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-08-31 18:22:06 -04:00
Tom Alexander
c43679fda9 Switch to local-path-provisioner.
Some checks failed
build Build build has started
clippy Build clippy has failed
rust-test Build rust-test has failed
format Build format has failed
2025-08-31 17:53:10 -04:00
Tom Alexander
9cc28f6f0d Merge branch 'unlisted_posts'
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-23 12:12:43 -05:00
Tom Alexander
d2256b8333 Add publish filter to blog posts and pages.
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2025-02-23 12:08:43 -05:00
Tom Alexander
fa8753077a Add support for unlisted posts.
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2025-02-23 12:02:14 -05:00
Tom Alexander
0420f58d02 Add a hover effect to the home link in the page header.
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-22 23:16:18 -05:00
Tom Alexander
0250aa106e Merge branch 'about_me'
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-22 22:54:18 -05:00
Tom Alexander
ca1c456571 Pass the nav links in the PageHeader render context.
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2025-02-22 22:49:59 -05:00
Tom Alexander
4403980e2e Add a me link to the nav bar. 2025-02-22 22:36:42 -05:00
Tom Alexander
dbfbce955d Merge branch 'tracing'
Some checks are pending
clippy Build clippy has started
format Build format has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-22 21:12:44 -05:00
Tom Alexander
2e08d2e59a Set up tracing. 2025-02-22 21:12:33 -05:00
Tom Alexander
9f14534c10 Merge branch 'source_code_highlighting'
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-22 19:56:47 -05:00
Tom Alexander
4e34ebc29e Reformat css.
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2025-02-22 19:45:04 -05:00
Tom Alexander
8d85d5ef79 Fix clippy.
All checks were successful
format Build format has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2025-02-22 19:42:24 -05:00
Tom Alexander
7d73a3c948 Clean up. 2025-02-22 19:24:05 -05:00
Tom Alexander
c501f7cedc Add syntax highlighting for bash.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2025-02-22 19:07:56 -05:00
Tom Alexander
41927764fc Continue highlights across code block lines. 2025-02-22 18:45:20 -05:00
Tom Alexander
75a763569b Disable ligatures in all code areas. 2025-02-22 18:27:45 -05:00
Tom Alexander
c67eb32774 Add more colors to python. 2025-02-22 18:26:33 -05:00
Tom Alexander
04952895cf Add support for highlighting python based on the nix highlighter. 2025-02-22 17:56:56 -05:00
Tom Alexander
749f6d7a55 Dynamically register which CSS files are needed. 2025-02-22 17:28:24 -05:00
Tom Alexander
c4cf814f8d Also add highlighting for paths. 2025-02-22 16:55:53 -05:00
Tom Alexander
3245e830d2 Assign more colors. 2025-02-22 16:46:47 -05:00
Tom Alexander
57eb1b81ec Start assigning colors. 2025-02-22 16:35:05 -05:00
Tom Alexander
c601c8697a Start a language-specific css file for highlight colors. 2025-02-22 16:25:36 -05:00
Tom Alexander
4ea1a46705 Update dust templates to support the new source code block format. 2025-02-22 16:13:23 -05:00
Tom Alexander
4cc04bda46 Update the render context to use the new src block format. 2025-02-22 16:04:47 -05:00
Tom Alexander
7e934cd360 Removed the enum and made plain src blocks just highlighted src blocks with only plain text in them. 2025-02-22 15:55:16 -05:00
Tom Alexander
e34e2ef75f Implement the highlighted src intermediate format.
I am going to remove the enum because I realized plain src blocks can just be highlighted src blocks with only RawText entries.
2025-02-22 15:44:45 -05:00
Tom Alexander
c067ca9cc8 Introduce an enum for a separate highlighted src block type. 2025-02-22 15:22:35 -05:00
Tom Alexander
b06424cb17 Initial highlighting code.
The dust auto-escaping is causing this naive approach to fail so I will have to create a distinction between highlighted code and not-highlighted code.
2025-02-22 15:09:00 -05:00
Tom Alexander
ae6f18d19c Center images when they are the only contents in a paragraph.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-22 13:15:58 -05:00
Tom Alexander
c371b999d5 Use the raw timestamp source for rendering timestamps.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-22 12:23:35 -05:00
Tom Alexander
073ac0ac25 Merge branch 'style_improvements'
Some checks failed
format Build format has succeeded
rust-test Build rust-test has succeeded
clippy Build clippy has failed
build Build build has succeeded
2025-02-18 07:19:25 -05:00
Tom Alexander
1c356737c1 Only add vertical padding for the top of the first blog post and use the same background color throughout.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2025-02-17 22:32:38 -05:00
Tom Alexander
339bd433f6 Add slight padding the blog posts.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2025-02-17 22:16:43 -05:00
Tom Alexander
bb5fa6a487 Swap site background and blog post background to give better contrast.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2025-02-17 22:12:08 -05:00
Tom Alexander
71b6db14d5 Add a background color for stand-alone blog posts. 2025-02-17 22:08:46 -05:00
Tom Alexander
69fb91db37 Remove padding on blog stream posts.
It was causing an ugly tail on blog stream posts.
2025-02-17 22:00:33 -05:00
Tom Alexander
5fd93fc648 Allow line wrapping even when contiguous words are really long.
The code blocks were causing horizontal scrolling on mobile.
2025-02-17 21:29:43 -05:00
Tom Alexander
033a17e355 Increase home link font size. 2025-02-17 21:29:43 -05:00
Tom Alexander
824f34bd4c Make everything box-sizing: border-box. 2025-02-17 20:57:40 -05:00
Tom Alexander
d93e91c625 Increase the font size for inline source blocks to the normal font size.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2025-02-17 20:54:56 -05:00
Tom Alexander
db21bd8a55 Restrict media to the size of its container.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2025-02-17 20:49:36 -05:00
Tom Alexander
69729bd329 Include viewport meta tag. 2025-02-17 20:49:36 -05:00
Tom Alexander
ddea8fdceb Increase title font-size.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
2025-02-17 19:03:48 -05:00
Tom Alexander
71f639e503 Fix size of code blocks in headlines and make the line spacing more pleasant.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
build Build build has succeeded
rust-test Build rust-test has succeeded
2025-02-16 20:09:17 -05:00
Tom Alexander
8714d3b650 Increase the division between posts.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-16 17:39:21 -05:00
Tom Alexander
88064409a6 Add support for PNGs.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-16 15:46:26 -05:00
Tom Alexander
7c17087920 Show bold text as bold.
Some checks failed
clippy Build clippy has failed
rust-test Build rust-test has succeeded
build Build build has succeeded
format Build format has succeeded
2025-02-16 11:45:52 -05:00
Tom Alexander
c1837addd0 Remove unused variable.
Some checks failed
format Build format has succeeded
clippy Build clippy has failed
rust-test Build rust-test has succeeded
build Build build has succeeded
2025-02-08 22:05:22 -05:00
66 changed files with 2094 additions and 206 deletions

View File

@@ -203,7 +203,7 @@ spec:
- name: git-source - name: git-source
volumeClaimTemplate: volumeClaimTemplate:
spec: spec:
storageClassName: "nfs-client" storageClassName: "local-path"
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:

View File

@@ -345,7 +345,7 @@ spec:
- name: git-source - name: git-source
volumeClaimTemplate: volumeClaimTemplate:
spec: spec:
storageClassName: "nfs-client" storageClassName: "local-path"
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:

View File

@@ -289,7 +289,7 @@ spec:
- name: git-source - name: git-source
volumeClaimTemplate: volumeClaimTemplate:
spec: spec:
storageClassName: "nfs-client" storageClassName: "local-path"
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:

View File

@@ -279,7 +279,7 @@ spec:
- name: git-source - name: git-source
volumeClaimTemplate: volumeClaimTemplate:
spec: spec:
storageClassName: "nfs-client" storageClassName: "local-path"
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:

1016
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "natter" name = "natter"
version = "0.0.1" version = "0.0.1"
edition = "2021" edition = "2024"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "A static site generator using org source files." description = "A static site generator using org source files."
license = "0BSD" license = "0BSD"
@@ -31,7 +31,21 @@ serde = { version = "1.0.189", default-features = false, features = ["std", "der
serde_json = "1.0.107" 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"] }
toml = "0.8.2" toml = "0.8.2"
tree-sitter-bash = "0.23.3"
tree-sitter-highlight = "0.25.2"
tree-sitter-nix = "0.0.2"
tree-sitter-python = "0.23.6"
url = "2.5.0" url = "2.5.0"
tracing = { version = "0.1.37", optional = true }
tracing-opentelemetry = { version = "0.20.0", optional = true }
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
opentelemetry-otlp = { version = "0.13.0", optional = true }
opentelemetry-semantic-conventions = { version = "0.12.0", optional = true }
[features]
default = ["tracing"]
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
# Optimized build for any sort of release. # Optimized build for any sort of release.
[profile.release-lto] [profile.release-lto]

21
TODO.org Normal file
View File

@@ -0,0 +1,21 @@
* Things to do [6/17]
** DONE If the paragraph only contains an image, text-align center
** DONE Syntax highlighting for code blocks
** TODO Render gnuplot
** TODO Pretty-print the timestamps
** TODO Support Table of Contents
** TODO Support line numbers in code blocks
** TODO Support references to code block lines
** TODO Only include text up to first heading on homepage and include a "read more" link
** DONE Make loading language-specific CSS files conditional on the presence of src blocks using those languages
** DONE Set up tracing so I can use warning and such
** TODO Make copying of language-specific CSS files conditional on the presence of src blocks using those languages
** TODO Switch to an entirely lazily-evaluated output tree
** TODO Add highlighting for languages [1/2]
*** DONE bash
*** TODO gnuplot
https://github.com/dpezto/tree-sitter-gnuplot is not on crates.io so I'd have to add a git dependency to use it. This would prevent publishing this crate to crates.io.
** DONE Bug: carry over highlight starts when breaking lines
** TODO Add dates to posts
** DONE Add support for unlisted posts (posts that do not show up on the homepage).
** TODO Add support for showing file name where we currently show language

View File

@@ -0,0 +1,39 @@
:root {
--srclg-bash-srchl-comment-color: #048a81;
--srclg-bash-srchl-function-color: #e95a62;
--srclg-bash-srchl-keyword-color: #1a936f;
--srclg-bash-srchl-property-color: inherit;
--srclg-bash-srchl-string-color: #ecc30b;
}
@media (prefers-color-scheme: light) {
:root {
--srclg-bash-srchl-comment-color: #fb757e;
--srclg-bash-srchl-function-color: #16a59d;
--srclg-bash-srchl-keyword-color: #e56c90;
--srclg-bash-srchl-property-color: inherit;
--srclg-bash-srchl-string-color: #133cf4;
}
}
.main_content {
.src_block {
&.srclg_bash {
.srchl_comment {
color: var(--srclg-bash-srchl-comment-color);
}
.srchl_function {
color: var(--srclg-bash-srchl-function-color);
}
.srchl_keyword {
color: var(--srclg-bash-srchl-keyword-color);
}
.srchl_property {
color: var(--srclg-bash-srchl-property-color);
}
.srchl_string {
color: var(--srclg-bash-srchl-string-color);
}
}
}
}

View File

@@ -0,0 +1,39 @@
:root {
--srclg-nix-srchl-keyword-color: #1a936f;
--srclg-nix-srchl-comment-color: #048a81;
--srclg-nix-srchl-property-color: #bfbccb;
--srclg-nix-srchl-string-color: #ecc30b;
--srclg-nix-srchl-string-special-path-color: #067bc2;
}
@media (prefers-color-scheme: light) {
:root {
--srclg-nix-srchl-keyword-color: #e56c90;
--srclg-nix-srchl-comment-color: #fb757e;
--srclg-nix-srchl-property-color: #404334;
--srclg-nix-srchl-string-color: #133cf4;
--srclg-nix-srchl-string-special-path-color: #f9843d;
}
}
.main_content {
.src_block {
&.srclg_nix {
.srchl_keyword {
color: var(--srclg-nix-srchl-keyword-color);
}
.srchl_comment {
color: var(--srclg-nix-srchl-comment-color);
}
.srchl_property {
color: var(--srclg-nix-srchl-property-color);
}
.srchl_string {
color: var(--srclg-nix-srchl-string-color);
}
.srchl_string_special_path {
color: var(--srclg-nix-srchl-string-special-path-color);
}
}
}
}

View File

@@ -0,0 +1,51 @@
/* ea912c */
/* e95a62 */
:root {
--srclg-python-srchl-comment-color: #048a81;
--srclg-python-srchl-function-builtin-color: #e95a62;
--srclg-python-srchl-keyword-color: #1a936f;
--srclg-python-srchl-property-color: inherit;
--srclg-python-srchl-string-color: #ecc30b;
--srclg-python-srchl-type-color: #067bc2;
--srclg-python-srchl-variable-color: #ea912c;
}
@media (prefers-color-scheme: light) {
:root {
--srclg-python-srchl-comment-color: #fb757e;
--srclg-python-srchl-function-builtin-color: #16a59d;
--srclg-python-srchl-keyword-color: #e56c90;
--srclg-python-srchl-property-color: inherit;
--srclg-python-srchl-string-color: #133cf4;
--srclg-python-srchl-type-color: #f9843d;
--srclg-python-srchl-variable-color: #156ed3;
}
}
.main_content {
.src_block {
&.srclg_python {
.srchl_comment {
color: var(--srclg-python-srchl-comment-color);
}
.srchl_function_builtin {
color: var(--srclg-python-srchl-function-builtin-color);
}
.srchl_keyword {
color: var(--srclg-python-srchl-keyword-color);
}
.srchl_property {
color: var(--srclg-python-srchl-property-color);
}
.srchl_string {
color: var(--srclg-python-srchl-string-color);
}
.srchl_type {
color: var(--srclg-python-srchl-type-color);
}
.srchl_variable {
color: var(--srclg-python-srchl-variable-color);
}
}
}
}

View File

@@ -5,7 +5,9 @@
--header-divider-color: #6a687a; --header-divider-color: #6a687a;
--stream-divider-color: #6ccff6; --stream-divider-color: #6ccff6;
--stream-post-background-color: #1f1f1f; --stream-post-background-color: #0a0a0a;
--blog-post-background-color: #0a0a0a;
--src-font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, --src-font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo,
Consolas, "DejaVu Sans Mono", monospace; Consolas, "DejaVu Sans Mono", monospace;
@@ -20,6 +22,13 @@
--table-border-color: #6a687a; --table-border-color: #6a687a;
--table-odd-background-color: #0a0a0a; --table-odd-background-color: #0a0a0a;
--table-even-background-color: #141414; --table-even-background-color: #141414;
--header-nav-regular-font-color: var(--site-text-color);
--header-nav-regular-background-color: var(--site-background-color);
--header-nav-hover-font-color: var(--site-background-color);
--header-nav-hover-background-color: var(--site-text-color);
--header-home-regular-font-color: var(--site-text-color);
--header-home-hover-font-color: #6ccff6;
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
@@ -29,7 +38,9 @@
--header-divider-color: #959785; --header-divider-color: #959785;
--stream-divider-color: #933009; --stream-divider-color: #933009;
--stream-post-background-color: #e0e0e0; --stream-post-background-color: #f5f5f5;
--blog-post-background-color: #f5f5f5;
--src-block-background-color: #ebebeb; --src-block-background-color: #ebebeb;
--src-block-border-color: #7b7d70; --src-block-border-color: #7b7d70;
@@ -41,9 +52,20 @@
--table-border-color: #959785; --table-border-color: #959785;
--table-odd-background-color: #f5f5f5; --table-odd-background-color: #f5f5f5;
--table-even-background-color: #ebebeb; --table-even-background-color: #ebebeb;
--header-nav-regular-font-color: var(--site-text-color);
--header-nav-regular-background-color: var(--site-background-color);
--header-nav-hover-font-color: var(--site-background-color);
--header-nav-hover-background-color: var(--site-text-color);
--header-home-regular-font-color: var(--site-text-color);
--header-home-hover-font-color: #933009;
} }
} }
* {
box-sizing: border-box;
}
body { body {
color: var(--site-text-color); color: var(--site-text-color);
background-color: var(--site-background-color); background-color: var(--site-background-color);
@@ -64,18 +86,57 @@ body {
} }
.page_header { .page_header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: stretch;
width: 100%; width: 100%;
max-width: var(--main-max-width); max-width: var(--main-max-width);
border-bottom: 0.1rem solid var(--header-divider-color); border-bottom: 0.1rem solid var(--header-divider-color);
.home_link { .home_link {
font-size: 1.2rem; display: block;
font-size: 2rem;
font-weight: 600; font-weight: 600;
text-decoration: none; text-decoration: none;
color: var(--header-home-regular-font-color);
transition-property: color;
transition-duration: 0.1s;
transition-timing-function: ease-out;
&:hover {
color: var(--header-home-hover-font-color) !important;
}
&:link, &:link,
&:visited { &:visited {
color: var(--site-text-color); color: inherit;
}
}
.header_nav_bar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: end;
align-items: stretch;
column-gap: 1rem;
.nav_link {
display: flex;
flex-direction: column;
justify-content: space-around;
color: var(--header-nav-regular-font-color);
background: var(--header-nav-regular-background-color);
padding: 0 0.5rem;
transition-property: background, color;
transition-duration: 0.1s;
transition-timing-function: ease-out;
&:hover {
color: var(--header-nav-hover-font-color);
background: var(--header-nav-hover-background-color);
}
} }
} }
} }
@@ -84,17 +145,19 @@ body {
width: 100%; width: 100%;
max-width: var(--main-max-width); max-width: var(--main-max-width);
font-size: 1.2rem; font-size: 1.2rem;
line-height: 1.2; line-height: 1.4;
padding-bottom: 8rem; padding-bottom: 8rem;
/* A stand-alone blog post (not in a blog stream). */ /* A stand-alone blog post (not in a blog stream). */
.blog_post { .blog_post {
padding: 1rem 0 3rem 0; padding: 1rem 0.2rem 0 0.2rem;
background: var(--blog-post-background-color);
} }
.blog_stream { .blog_stream {
.stream_divider { .stream_divider {
color: var(--stream-divider-color); color: var(--stream-divider-color);
margin: 40px 0;
} }
.stream_nav { .stream_nav {
@@ -117,17 +180,29 @@ body {
/* A blog post in a blog stream (for example, the homepage). */ /* A blog post in a blog stream (for example, the homepage). */
.blog_stream_post { .blog_stream_post {
background: var(--stream-post-background-color); background: var(--stream-post-background-color);
padding: 1rem 0.2rem; padding: 0 0.2rem;
}
.blog_stream_post {
background: var(--stream-post-background-color);
}
.blog_stream_post:nth-child(1) {
padding-top: 1rem;
} }
.blog_post_title { .blog_post_title {
font-size: 2.5rem; font-size: 2.9rem;
font-weight: 700; font-weight: 700;
padding-bottom: 1rem; padding-bottom: 1rem;
} }
p { p {
margin: 1rem 0; margin: 1rem 0;
&.image {
text-align: center;
}
} }
.src_block { .src_block {
@@ -137,6 +212,7 @@ body {
font-size: 1rem; font-size: 1rem;
font-family: var(--src-font-family); font-family: var(--src-font-family);
margin: 1rem 0; margin: 1rem 0;
font-variant-ligatures: none;
.src_language { .src_language {
display: inline-block; display: inline-block;
@@ -153,19 +229,21 @@ body {
.src_line { .src_line {
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: anywhere;
} }
} }
} }
.inline_source_block { .inline_source_block {
font-family: var(--src-font-family); font-family: var(--src-font-family);
font-size: 1rem; font-size: 1.2rem;
font-variant-ligatures: none;
} }
.code, .code,
.verbatim { .verbatim {
font-family: var(--src-font-family); font-family: var(--src-font-family);
font-size: 1rem; font-variant-ligatures: none;
} }
.quote_block { .quote_block {
@@ -266,4 +344,15 @@ body {
} }
} }
} }
b {
font-weight: 700;
}
/* Never have media larger than its container */
img,
picture,
video {
max-width: 100%;
}
} }

View File

@@ -1,7 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{#global_settings.css_files}<link rel="stylesheet" href="{.}">{/global_settings.css_files} {#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.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} {?global_settings.page_title}<title>{global_settings.page_title}</title>{/global_settings.page_title}

View File

@@ -1,4 +1,9 @@
<header class="page_header"> <header class="page_header">
<a class="home_link" href="{.home_link}">{.website_title}</a> <a class="home_link" href="{.home_link}">{.website_title}</a>
{! TODO: Additional links? Probably using the nav semantic element. !} {! TODO: Additional links? Probably using the nav semantic element. !}
<nav class="header_nav_bar">
{#.nav_links}
<a class="nav_link" href="{.url}"><div>{.text}</div></a>
{/.nav_links}
</nav>
</header> </header>

View File

@@ -1,3 +1,3 @@
<p>{#.children} <p class="{?.is_single_image}image{/.is_single_image}">{#.children}
{>object/} {>object/}
{/.children}</p> {/.children}</p>

View File

@@ -1,10 +1,15 @@
<div class="src_block"> <div class="src_block{?.language} srclg_{.language}{/.language}">
{?.language}<div class="src_language">{.language}</div>{/.language} {?.language}<div class="src_language">{.language}</div>{/.language}
<table class="src_body"> <table class="src_body">
<tbody> <tbody>
{#.lines} {#.lines}
<tr> <tr>
<td><code class="src_line">{.}</code></td> <td>{#.children}{@select key=.type}
{@eq value="raw_text"}<code class="src_line">{.content}</code>{/eq}
{@eq value="highlight_start"}<span class="srchl_{.name}">{/eq}
{@eq value="highlight_end"}</span>{/eq}
{@none}{!TODO: make this panic!}ERROR: Unrecognized type {.type}.{/none}
{/select}{/.children}</td>
</tr> </tr>
{/.lines} {/.lines}
</tbody> </tbody>

View File

@@ -1 +1 @@
!!!!!!!! timestamp <span class="timestamp">{.source}</span>

View File

@@ -1,8 +1,8 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::PathBuf; use std::path::PathBuf;
use include_dir::include_dir;
use include_dir::Dir; use include_dir::Dir;
use include_dir::include_dir;
use tokio::fs::DirEntry; use tokio::fs::DirEntry;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@@ -15,14 +15,15 @@ use crate::context::RenderBlogStreamInput;
use crate::context::RenderContext; use crate::context::RenderContext;
use crate::context::RenderPage; use crate::context::RenderPage;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost; use crate::intermediate::BlogPost;
use crate::intermediate::IPage; use crate::intermediate::IPage;
use crate::intermediate::PublishStatus;
use crate::intermediate::get_web_path;
use crate::render::DusterRenderer; use crate::render::DusterRenderer;
use crate::render::RendererIntegration; use crate::render::RendererIntegration;
use crate::walk_fs::walk_fs;
use crate::walk_fs::WalkAction; use crate::walk_fs::WalkAction;
use crate::walk_fs::WalkFsFilterResult; use crate::walk_fs::WalkFsFilterResult;
use crate::walk_fs::walk_fs;
use super::stylesheet::Stylesheet; use super::stylesheet::Stylesheet;
@@ -84,7 +85,11 @@ impl SiteRenderer {
pub(crate) async fn render_pages(&self, config: &Config) -> Result<(), CustomError> { pub(crate) async fn render_pages(&self, config: &Config) -> Result<(), CustomError> {
let renderer_integration = self.init_renderer_integration()?; let renderer_integration = self.init_renderer_integration()?;
for page in &self.pages { for page in self.pages.iter().filter(|page| match page.natter_publish {
PublishStatus::Full => true,
PublishStatus::Unlisted => true,
PublishStatus::Unpublished => false,
}) {
let output_path = self.output_directory.join(page.get_output_path()); let output_path = self.output_directory.join(page.get_output_path());
let dependency_manager = let dependency_manager =
std::sync::Arc::new(std::sync::Mutex::new(DependencyManager::new())); std::sync::Arc::new(std::sync::Mutex::new(DependencyManager::new()));
@@ -115,7 +120,17 @@ impl SiteRenderer {
pub(crate) async fn render_blog_posts(&self, config: &Config) -> Result<(), CustomError> { pub(crate) async fn render_blog_posts(&self, config: &Config) -> Result<(), CustomError> {
let renderer_integration = self.init_renderer_integration()?; let renderer_integration = self.init_renderer_integration()?;
for blog_post in &self.blog_posts { for blog_post in self.blog_posts.iter().filter(|blog_post| {
match blog_post
.get_index_page()
.expect("Blog posts should have an index page.")
.natter_publish
{
PublishStatus::Full => true,
PublishStatus::Unlisted => true,
PublishStatus::Unpublished => false,
}
}) {
for blog_post_page in &blog_post.pages { for blog_post_page in &blog_post.pages {
let output_path = self let output_path = self
.output_directory .output_directory
@@ -155,7 +170,21 @@ impl SiteRenderer {
// Sort blog posts by date, newest first. // Sort blog posts by date, newest first.
let sorted_blog_posts = { let sorted_blog_posts = {
let mut sorted_blog_posts: Vec<_> = self.blog_posts.iter().collect(); let mut sorted_blog_posts: Vec<_> = self
.blog_posts
.iter()
.filter(|blog_post| {
match blog_post
.get_index_page()
.expect("Blog posts should have an index page.")
.natter_publish
{
PublishStatus::Full => true,
PublishStatus::Unlisted => false,
PublishStatus::Unpublished => false,
}
})
.collect();
sorted_blog_posts sorted_blog_posts
.sort_by_key(|blog_post| (blog_post.get_date(), blog_post.id.as_str())); .sort_by_key(|blog_post| (blog_post.get_date(), blog_post.id.as_str()));
sorted_blog_posts.reverse(); sorted_blog_posts.reverse();

View File

@@ -8,17 +8,17 @@ use crate::cli::parameters::BuildArgs;
use crate::command::build::render::SiteRenderer; use crate::command::build::render::SiteRenderer;
use crate::config::Config; use crate::config::Config;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::get_org_files;
use crate::intermediate::BlogPost; use crate::intermediate::BlogPost;
use crate::intermediate::IPage; use crate::intermediate::IPage;
use crate::intermediate::IntermediateContext; use crate::intermediate::IntermediateContext;
use crate::intermediate::PageInput; use crate::intermediate::PageInput;
use crate::intermediate::Registry; use crate::intermediate::Registry;
use crate::walk_fs::walk_fs; use crate::intermediate::get_org_files;
use crate::walk_fs::WalkAction; use crate::walk_fs::WalkAction;
use crate::walk_fs::WalkFsFilterResult; use crate::walk_fs::WalkFsFilterResult;
use include_dir::include_dir; use crate::walk_fs::walk_fs;
use include_dir::Dir; use include_dir::Dir;
use include_dir::include_dir;
use tokio::fs::DirEntry; use tokio::fs::DirEntry;
static DEFAULT_STYLESHEETS: Dir = static DEFAULT_STYLESHEETS: Dir =

View File

@@ -3,6 +3,8 @@ use serde::Serialize;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IAstNode; use crate::intermediate::IAstNode;
use super::RenderHeading;
use super::RenderSection;
use super::angle_link::RenderAngleLink; use super::angle_link::RenderAngleLink;
use super::babel_call::RenderBabelCall; use super::babel_call::RenderBabelCall;
use super::bold::RenderBold; use super::bold::RenderBold;
@@ -55,8 +57,6 @@ use super::timestamp::RenderTimestamp;
use super::underline::RenderUnderline; use super::underline::RenderUnderline;
use super::verbatim::RenderVerbatim; use super::verbatim::RenderVerbatim;
use super::verse_block::RenderVerseBlock; use super::verse_block::RenderVerseBlock;
use super::RenderHeading;
use super::RenderSection;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(untagged)] #[serde(untagged)]

View File

@@ -1,17 +1,19 @@
use std::collections::HashSet;
use serde::Serialize; use serde::Serialize;
use super::render_context::RenderContext; use super::render_context::RenderContext;
use crate::context::macros::push_file; use crate::context::macros::push_file;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost; use crate::intermediate::BlogPost;
use crate::intermediate::BlogPostPage; use crate::intermediate::BlogPostPage;
use crate::intermediate::get_web_path;
use super::footnote_definition::RenderRealFootnoteDefinition;
use super::macros::render;
use super::GlobalSettings; use super::GlobalSettings;
use super::PageHeader; use super::PageHeader;
use super::RenderDocumentElement; use super::RenderDocumentElement;
use super::footnote_definition::RenderRealFootnoteDefinition;
use super::macros::render;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct RenderBlogPostPageInput<'a> { pub(crate) struct RenderBlogPostPageInput<'a> {
@@ -51,28 +53,6 @@ render!(
render_context, render_context,
{ {
push_file!(render_context, &original.page.src, { push_file!(render_context, &original.page.src, {
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( let page_header = PageHeader::new(
render_context.config.get_site_title().map(str::to_string), render_context.config.get_site_title().map(str::to_string),
Some(get_web_path( Some(get_web_path(
@@ -81,6 +61,12 @@ render!(
render_context.output_file, render_context.output_file,
"", "",
)?), )?),
Some(get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"about_me",
)?),
); );
let link_to_blog_post = get_web_path( let link_to_blog_post = get_web_path(
render_context.config, render_context.config,
@@ -114,6 +100,46 @@ render!(
ret ret
}; };
let mut 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 additional_css_files = render_context
.dependency_manager
.lock()
.unwrap()
.list_css()?
.map(|css_name| {
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
format!("stylesheet/{}", css_name),
)
})
.collect::<Result<HashSet<_>, _>>()?;
css_files.extend(additional_css_files.into_iter());
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 ret = RenderBlogPostPage { let ret = RenderBlogPostPage {
global_settings, global_settings,
page_header: Some(page_header), page_header: Some(page_header),

View File

@@ -1,13 +1,15 @@
use std::collections::HashSet;
use serde::Serialize; use serde::Serialize;
use super::macros::render; use super::macros::render;
use super::render_context::RenderContext; use super::render_context::RenderContext;
use crate::context::macros::push_file;
use crate::context::RenderDocumentElement; use crate::context::RenderDocumentElement;
use crate::context::RenderRealFootnoteDefinition; use crate::context::RenderRealFootnoteDefinition;
use crate::context::macros::push_file;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::BlogPost; use crate::intermediate::BlogPost;
use crate::intermediate::get_web_path;
use super::GlobalSettings; use super::GlobalSettings;
use super::PageHeader; use super::PageHeader;
@@ -49,31 +51,6 @@ render!(
original, original,
render_context, 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( let page_header = PageHeader::new(
render_context.config.get_site_title().map(str::to_string), render_context.config.get_site_title().map(str::to_string),
Some(get_web_path( Some(get_web_path(
@@ -82,6 +59,12 @@ render!(
render_context.output_file, render_context.output_file,
"", "",
)?), )?),
Some(get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"about_me",
)?),
); );
let children = original let children = original
@@ -105,6 +88,49 @@ render!(
None None
}; };
let mut 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 additional_css_files = render_context
.dependency_manager
.lock()
.unwrap()
.list_css()?
.map(|css_name| {
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
format!("stylesheet/{}", css_name),
)
})
.collect::<Result<HashSet<_>, _>>()?;
css_files.extend(additional_css_files.into_iter());
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,
);
Ok(RenderBlogStream { Ok(RenderBlogStream {
global_settings, global_settings,
page_header: Some(page_header), page_header: Some(page_header),

View File

@@ -3,9 +3,9 @@ use serde::Serialize;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IBold; use crate::intermediate::IBold;
use super::RenderObject;
use super::macros::render; use super::macros::render;
use super::render_context::RenderContext; use super::render_context::RenderContext;
use super::RenderObject;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -7,6 +7,7 @@ use super::RenderContext;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum Dependency { pub(crate) enum Dependency {
StaticFile { absolute_path: PathBuf }, StaticFile { absolute_path: PathBuf },
CssFile { name: String },
} }
impl Dependency { impl Dependency {
@@ -40,6 +41,11 @@ impl Dependency {
} }
Ok(()) Ok(())
} }
Dependency::CssFile { name: _ } => {
// We don't do anything because the CSS files are already copied into the output from natter's default environment.
// TODO: When we add support for CSS outside the default environment, we should add support for dynamically picking which ones to copy here.
Ok(())
}
} }
} }
} }

View File

@@ -65,4 +65,20 @@ impl DependencyManager {
std::mem::swap(&mut self.dependencies, &mut dependencies); std::mem::swap(&mut self.dependencies, &mut dependencies);
dependencies dependencies
} }
pub(crate) fn include_css<N>(&mut self, name: N) -> Result<(), CustomError>
where
std::string::String: From<N>,
{
self.dependencies
.push(Dependency::CssFile { name: name.into() });
Ok(())
}
pub(crate) fn list_css(&self) -> Result<impl Iterator<Item = &String>, CustomError> {
Ok(self.dependencies.iter().filter_map(|dep| match dep {
Dependency::CssFile { name } => Some(name),
_ => None,
}))
}
} }

View File

@@ -4,9 +4,9 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IDocumentElement; use crate::intermediate::IDocumentElement;
use super::macros::render;
use super::RenderHeading; use super::RenderHeading;
use super::RenderSection; use super::RenderSection;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(untagged)] #[serde(untagged)]

View File

@@ -4,9 +4,9 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IHeading; use crate::intermediate::IHeading;
use super::macros::render;
use super::RenderDocumentElement; use super::RenderDocumentElement;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -4,8 +4,8 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IItalic; use crate::intermediate::IItalic;
use super::macros::render;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -1,13 +1,15 @@
use super::footnote_definition::RenderRealFootnoteDefinition; use std::collections::HashSet;
use super::macros::render;
use super::render_context::RenderContext;
use super::GlobalSettings; use super::GlobalSettings;
use super::PageHeader; use super::PageHeader;
use super::RenderDocumentElement; use super::RenderDocumentElement;
use super::footnote_definition::RenderRealFootnoteDefinition;
use super::macros::render;
use super::render_context::RenderContext;
use crate::context::macros::push_file; use crate::context::macros::push_file;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::get_web_path;
use crate::intermediate::IPage; use crate::intermediate::IPage;
use crate::intermediate::get_web_path;
use serde::Serialize; use serde::Serialize;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@@ -30,27 +32,6 @@ pub(crate) struct RenderPage {
render!(RenderPage, IPage, original, render_context, { render!(RenderPage, IPage, original, render_context, {
push_file!(render_context, &original.src, { push_file!(render_context, &original.src, {
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.title.clone(), css_files, js_files);
let page_header = PageHeader::new( let page_header = PageHeader::new(
render_context.config.get_site_title().map(str::to_string), render_context.config.get_site_title().map(str::to_string),
Some(get_web_path( Some(get_web_path(
@@ -59,6 +40,12 @@ render!(RenderPage, IPage, original, render_context, {
render_context.output_file, render_context.output_file,
"", "",
)?), )?),
Some(get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
"about_me",
)?),
); );
let link_to_blog_post = get_web_path( let link_to_blog_post = get_web_path(
render_context.config, render_context.config,
@@ -92,6 +79,45 @@ render!(RenderPage, IPage, original, render_context, {
ret ret
}; };
let mut 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 additional_css_files = render_context
.dependency_manager
.lock()
.unwrap()
.list_css()?
.map(|css_name| {
get_web_path(
render_context.config,
render_context.output_root_directory,
render_context.output_file,
format!("stylesheet/{}", css_name),
)
})
.collect::<Result<HashSet<_>, _>>()?;
css_files.extend(additional_css_files.into_iter());
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.title.clone(), css_files, js_files);
let ret = RenderPage { let ret = RenderPage {
global_settings, global_settings,
page_header: Some(page_header), page_header: Some(page_header),

View File

@@ -7,13 +7,35 @@ use serde::Serialize;
pub(crate) struct PageHeader { pub(crate) struct PageHeader {
website_title: Option<String>, website_title: Option<String>,
home_link: Option<String>, home_link: Option<String>,
nav_links: Vec<NavLink>,
}
/// A link in the top-right of the page.
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
#[serde(rename = "nav_link")]
pub(crate) struct NavLink {
text: Option<String>,
url: Option<String>,
} }
impl PageHeader { impl PageHeader {
pub(crate) fn new(website_title: Option<String>, home_link: Option<String>) -> PageHeader { pub(crate) fn new(
website_title: Option<String>,
home_link: Option<String>,
about_me_link: Option<String>,
) -> PageHeader {
PageHeader { PageHeader {
website_title, website_title,
home_link, home_link,
nav_links: about_me_link
.map(|url| {
vec![NavLink {
text: Some("About Me".to_owned()),
url: Some(url),
}]
})
.unwrap_or_default(),
} }
} }
} }

View File

@@ -4,14 +4,15 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IParagraph; use crate::intermediate::IParagraph;
use super::macros::render;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
#[serde(rename = "paragraph")] #[serde(rename = "paragraph")]
pub(crate) struct RenderParagraph { pub(crate) struct RenderParagraph {
children: Vec<RenderObject>, children: Vec<RenderObject>,
is_single_image: bool,
post_blank: organic::types::PostBlank, post_blank: organic::types::PostBlank,
} }
@@ -26,6 +27,7 @@ render!(RenderParagraph, IParagraph, original, render_context, {
Ok(RenderParagraph { Ok(RenderParagraph {
children, children,
is_single_image: original.is_single_image(),
post_blank: original.post_blank, post_blank: original.post_blank,
}) })
}); });

View File

@@ -4,9 +4,9 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IPlainListItem; use crate::intermediate::IPlainListItem;
use super::macros::render;
use super::RenderElement; use super::RenderElement;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -4,8 +4,8 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IPlainListSimpleItem; use crate::intermediate::IPlainListSimpleItem;
use super::macros::render;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
/// Special case for list items with only paragraphs and sublists as their children. In those cases, the paragraph tags are omitted. This is equivalent to a Paragraph but in a different struct to have a different type tag. /// Special case for list items with only paragraphs and sublists as their children. In those cases, the paragraph tags are omitted. This is equivalent to a Paragraph but in a different struct to have a different type tag.
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]

View File

@@ -4,8 +4,8 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IQuoteBlock; use crate::intermediate::IQuoteBlock;
use super::macros::render;
use super::RenderElement; use super::RenderElement;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -5,8 +5,8 @@ use crate::error::CustomError;
use crate::intermediate::IRegularLink; use crate::intermediate::IRegularLink;
use crate::intermediate::LinkTarget; use crate::intermediate::LinkTarget;
use super::macros::render;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -4,8 +4,8 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::ISection; use crate::intermediate::ISection;
use super::macros::render;
use super::RenderElement; use super::RenderElement;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -3,6 +3,7 @@ use serde::Serialize;
use super::render_context::RenderContext; use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::ISrcBlock; use crate::intermediate::ISrcBlock;
use crate::intermediate::ISrcSegment;
use super::macros::render; use super::macros::render;
@@ -10,15 +11,85 @@ use super::macros::render;
#[serde(tag = "type")] #[serde(tag = "type")]
#[serde(rename = "src_block")] #[serde(rename = "src_block")]
pub(crate) struct RenderSrcBlock { pub(crate) struct RenderSrcBlock {
lines: Vec<String>, lines: Vec<RenderSrcLine>,
language: Option<String>, language: Option<String>,
post_blank: organic::types::PostBlank, post_blank: organic::types::PostBlank,
} }
render!(RenderSrcBlock, ISrcBlock, original, _render_context, { #[derive(Debug, Serialize)]
pub(crate) struct RenderSrcLine {
children: Vec<RenderSrcSegment>,
}
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
pub(crate) enum RenderSrcSegment {
#[serde(rename = "raw_text")]
RawText { content: String },
#[serde(rename = "highlight_start")]
HighlightStart { name: String },
#[serde(rename = "highlight_end")]
HighlightEnd,
}
render!(RenderSrcBlock, ISrcBlock, original, render_context, {
let lines = original
.lines
.iter()
.map(|original_line| {
let children = original_line
.children
.iter()
.map(|original_segment| match original_segment {
ISrcSegment::RawText(body) => RenderSrcSegment::RawText {
content: body.to_owned(),
},
ISrcSegment::HighlightStart { name } => RenderSrcSegment::HighlightStart {
name: css_safe_name(name),
},
ISrcSegment::HighlightEnd => RenderSrcSegment::HighlightEnd,
})
.collect();
RenderSrcLine { children }
})
.collect();
match original.language.as_deref() {
Some("bash") => {
render_context
.dependency_manager
.lock()
.unwrap()
.include_css("language_bash.css")?;
}
Some("nix") => {
render_context
.dependency_manager
.lock()
.unwrap()
.include_css("language_nix.css")?;
}
Some("python") => {
render_context
.dependency_manager
.lock()
.unwrap()
.include_css("language_python.css")?;
}
_ => {}
};
Ok(RenderSrcBlock { Ok(RenderSrcBlock {
lines: original.lines.clone(), lines,
language: original.language.clone(), language: original.language.clone(),
post_blank: original.post_blank, post_blank: original.post_blank,
}) })
}); });
fn css_safe_name<S>(inp: S) -> String
where
std::string::String: From<S>,
{
let inp: String = inp.into();
inp.replace(".", "_")
}

View File

@@ -4,8 +4,8 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IStrikeThrough; use crate::intermediate::IStrikeThrough;
use super::macros::render;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -4,8 +4,8 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::ITableCell; use crate::intermediate::ITableCell;
use super::macros::render;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@@ -1,16 +1,21 @@
use serde::Serialize; use serde::Serialize;
use super::macros::render;
use super::render_context::RenderContext; use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::ITimestamp; use crate::intermediate::ITimestamp;
use super::macros::rnoop;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
#[serde(rename = "timestamp")] #[serde(rename = "timestamp")]
pub(crate) struct RenderTimestamp { pub(crate) struct RenderTimestamp {
source: String,
post_blank: organic::types::PostBlank, post_blank: organic::types::PostBlank,
} }
rnoop!(RenderTimestamp, ITimestamp); render!(RenderTimestamp, ITimestamp, original, _render_context, {
Ok(RenderTimestamp {
source: original.source.clone(),
post_blank: original.post_blank,
})
});

View File

@@ -4,8 +4,8 @@ use super::render_context::RenderContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IUnderline; use crate::intermediate::IUnderline;
use super::macros::render;
use super::RenderObject; use super::RenderObject;
use super::macros::render;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

88
src/init_tracing.rs Normal file
View File

@@ -0,0 +1,88 @@
#[cfg(feature = "tracing")]
use opentelemetry_otlp::WithExportConfig;
#[cfg(feature = "tracing")]
use tracing::warn;
#[cfg(feature = "tracing")]
use tracing_subscriber::fmt;
#[cfg(feature = "tracing")]
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
#[cfg(feature = "tracing")]
use tracing_subscriber::util::SubscriberInitExt;
const SERVICE_NAME: &str = "natter";
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.
#[cfg(feature = "tracing")]
pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
let log_to_console = fmt::layer();
let subscriber = tracing_subscriber::Registry::default();
let level_filter_layer = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or(tracing_subscriber::EnvFilter::new("WARN"));
// by default it will hit http://localhost:4317 with a gRPC payload
// TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned.
let exporter = opentelemetry_otlp::new_exporter()
.tonic()
// Using "localhost" is broken inside the docker container when tracing
.with_endpoint("http://127.0.0.1:4317/v1/traces");
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(exporter)
.with_trace_config(opentelemetry::sdk::trace::config().with_resource(
opentelemetry::sdk::Resource::new(vec![opentelemetry::KeyValue::new(
opentelemetry_semantic_conventions::resource::SERVICE_NAME,
SERVICE_NAME.to_string(),
)]),
))
// If I do install_batch then 1K+ spans will get orphaned off into their own trace and I get the error message "OpenTelemetry trace error occurred. cannot send message to batch processor as the channel is closed"
//
// If I do install_simple then it only creates 1 trace (which is good!) but my console gets spammed with this concerning log message that makes me think it might be dropping the extra spans on the floor: "OpenTelemetry trace error occurred. Exporter otlp encountered the following error(s): the grpc server returns error (Unknown error): , detailed error message: Service was not ready: transport error"
//
// I suspect it is related to this bug: https://github.com/open-telemetry/opentelemetry-rust/issues/888
//
// .install_simple()
.install_batch(opentelemetry::runtime::Tokio);
let tracing_layer = tracer.map(|tracer| tracing_opentelemetry::layer().with_tracer(tracer));
opentelemetry::global::set_text_map_propagator(
opentelemetry::sdk::propagation::TraceContextPropagator::new(),
);
match tracing_layer {
Ok(tracing_layer) => {
subscriber
.with(level_filter_layer)
.with(tracing_layer)
.with(log_to_console)
.try_init()?;
}
Err(e) => {
subscriber
.with(level_filter_layer)
.with(fmt::layer())
.try_init()?;
warn!("Failed initialize OpenTelemetry tracing: {}", e);
}
};
Ok(())
}
#[cfg(feature = "tracing")]
pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
opentelemetry::global::shutdown_tracer_provider();
Ok(())
}
#[cfg(not(feature = "tracing"))]
pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[cfg(not(feature = "tracing"))]
pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

View File

@@ -1,28 +1,3 @@
use super::angle_link::IAngleLink;
use super::bold::IBold;
use super::citation::ICitation;
use super::citation_reference::ICitationReference;
use super::code::ICode;
use super::comment::IComment;
use super::entity::IEntity;
use super::export_snippet::IExportSnippet;
use super::footnote_reference::IFootnoteReference;
use super::inline_babel_call::IInlineBabelCall;
use super::inline_source_block::IInlineSourceBlock;
use super::italic::IItalic;
use super::keyword::IKeyword;
use super::latex_fragment::ILatexFragment;
use super::line_break::ILineBreak;
use super::org_macro::IOrgMacro;
use super::plain_link::IPlainLink;
use super::plain_text::IPlainText;
use super::radio_link::IRadioLink;
use super::radio_target::IRadioTarget;
use super::regular_link::IRegularLink;
use super::statistics_cookie::IStatisticsCookie;
use super::strike_through::IStrikeThrough;
use super::subscript::ISubscript;
use super::superscript::ISuperscript;
use super::IBabelCall; use super::IBabelCall;
use super::ICenterBlock; use super::ICenterBlock;
use super::IClock; use super::IClock;
@@ -52,6 +27,31 @@ use super::IUnderline;
use super::IVerbatim; use super::IVerbatim;
use super::IVerseBlock; use super::IVerseBlock;
use super::IntermediateContext; use super::IntermediateContext;
use super::angle_link::IAngleLink;
use super::bold::IBold;
use super::citation::ICitation;
use super::citation_reference::ICitationReference;
use super::code::ICode;
use super::comment::IComment;
use super::entity::IEntity;
use super::export_snippet::IExportSnippet;
use super::footnote_reference::IFootnoteReference;
use super::inline_babel_call::IInlineBabelCall;
use super::inline_source_block::IInlineSourceBlock;
use super::italic::IItalic;
use super::keyword::IKeyword;
use super::latex_fragment::ILatexFragment;
use super::line_break::ILineBreak;
use super::org_macro::IOrgMacro;
use super::plain_link::IPlainLink;
use super::plain_text::IPlainText;
use super::radio_link::IRadioLink;
use super::radio_target::IRadioTarget;
use super::regular_link::IRegularLink;
use super::statistics_cookie::IStatisticsCookie;
use super::strike_through::IStrikeThrough;
use super::subscript::ISubscript;
use super::superscript::ISuperscript;
use crate::error::CustomError; use crate::error::CustomError;
use futures::future::{BoxFuture, FutureExt}; use futures::future::{BoxFuture, FutureExt};

View File

@@ -7,12 +7,12 @@ use tokio::fs::DirEntry;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use crate::error::CustomError; use crate::error::CustomError;
use crate::intermediate::IntermediateContext;
use crate::intermediate::blog_post_page::BlogPostPageInput; use crate::intermediate::blog_post_page::BlogPostPageInput;
use crate::intermediate::registry::Registry; use crate::intermediate::registry::Registry;
use crate::intermediate::IntermediateContext;
use crate::walk_fs::walk_fs;
use crate::walk_fs::WalkAction; use crate::walk_fs::WalkAction;
use crate::walk_fs::WalkFsFilterResult; use crate::walk_fs::WalkFsFilterResult;
use crate::walk_fs::walk_fs;
use super::BlogPostPage; use super::BlogPostPage;

View File

@@ -4,10 +4,10 @@ use crate::error::CustomError;
use super::footnote_definition::IRealFootnoteDefinition; use super::footnote_definition::IRealFootnoteDefinition;
use super::macros::intermediate;
use super::IDocumentElement; use super::IDocumentElement;
use super::IHeading; use super::IHeading;
use super::ISection; use super::ISection;
use super::macros::intermediate;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct BlogPostPageInput<'b, 'parse> { pub(crate) struct BlogPostPageInput<'b, 'parse> {
@@ -48,6 +48,16 @@ pub(crate) struct BlogPostPage {
pub(crate) children: Vec<IDocumentElement>, pub(crate) children: Vec<IDocumentElement>,
pub(crate) footnotes: Vec<IRealFootnoteDefinition>, pub(crate) footnotes: Vec<IRealFootnoteDefinition>,
pub(crate) natter_publish: PublishStatus,
}
#[derive(Debug, Default)]
pub(crate) enum PublishStatus {
#[default]
Full,
Unlisted,
Unpublished,
} }
intermediate!( intermediate!(
@@ -93,6 +103,7 @@ intermediate!(
date: get_date(original.document), date: get_date(original.document),
children, children,
footnotes, footnotes,
natter_publish: get_publish_status(original.document).unwrap_or_default(),
}) })
} }
); );
@@ -129,3 +140,25 @@ pub(crate) fn get_date(document: &organic::types::Document<'_>) -> Option<String
.last() .last()
.map(|kw| kw.value.to_owned()) .map(|kw| kw.value.to_owned())
} }
pub(crate) fn get_publish_status(document: &organic::types::Document<'_>) -> Option<PublishStatus> {
let publish_string = organic::types::AstNode::from(document)
.iter_all_ast_nodes()
.filter_map(|node| match node {
organic::types::AstNode::Keyword(kw)
if kw.key.eq_ignore_ascii_case("natter_publish") =>
{
Some(kw)
}
_ => None,
})
.last()
.map(|kw| kw.value);
match publish_string {
Some("full") => Some(PublishStatus::Full),
Some("unlisted") => Some(PublishStatus::Unlisted),
Some("unpublished") => Some(PublishStatus::Unpublished),
Some(status) => panic!("Unrecognized publish status: {}", status),
None => None,
}
}

View File

@@ -1,7 +1,7 @@
use organic::types::StandardProperties; use organic::types::StandardProperties;
use super::macros::intermediate;
use super::IObject; use super::IObject;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;

View File

@@ -1,7 +1,7 @@
use super::IPlainListSimpleItem;
use super::comment::IComment; use super::comment::IComment;
use super::keyword::IKeyword; use super::keyword::IKeyword;
use super::macros::iselector; use super::macros::iselector;
use super::IPlainListSimpleItem;
use super::IBabelCall; use super::IBabelCall;
use super::ICenterBlock; use super::ICenterBlock;

View File

@@ -1,7 +1,7 @@
use super::macros::intermediate;
use super::registry::register_footnote_definition;
use super::IAstNode; use super::IAstNode;
use super::IntermediateContext; use super::IntermediateContext;
use super::macros::intermediate;
use super::registry::register_footnote_definition;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;
@@ -32,6 +32,7 @@ pub(crate) struct IRealFootnoteDefinition {
} }
impl IRealFootnoteDefinition { impl IRealFootnoteDefinition {
#[allow(clippy::needless_lifetimes)]
pub(crate) async fn new<'orig, 'parse>( pub(crate) async fn new<'orig, 'parse>(
_intermediate_context: IntermediateContext<'orig, 'parse>, _intermediate_context: IntermediateContext<'orig, 'parse>,
footnote_id: usize, footnote_id: usize,

View File

@@ -1,6 +1,6 @@
use super::macros::intermediate;
use super::IDocumentElement; use super::IDocumentElement;
use super::IObject; use super::IObject;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;

View File

@@ -1,7 +1,7 @@
use organic::types::StandardProperties; use organic::types::StandardProperties;
use super::macros::intermediate;
use super::IObject; use super::IObject;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;

View File

@@ -9,6 +9,7 @@ macro_rules! inoop {
} }
impl $istruct { impl $istruct {
#[allow(clippy::extra_unused_lifetimes)]
pub(crate) async fn new<'reg, 'orig, 'parse>( pub(crate) async fn new<'reg, 'orig, 'parse>(
_intermediate_context: crate::intermediate::IntermediateContext<'orig, 'parse>, _intermediate_context: crate::intermediate::IntermediateContext<'orig, 'parse>,
original: &'orig organic::types::$pstruct<'parse>, original: &'orig organic::types::$pstruct<'parse>,

View File

@@ -71,9 +71,10 @@ mod verse_block;
pub(crate) use angle_link::IAngleLink; pub(crate) use angle_link::IAngleLink;
pub(crate) use ast_node::IAstNode; pub(crate) use ast_node::IAstNode;
pub(crate) use babel_call::IBabelCall; pub(crate) use babel_call::IBabelCall;
pub(crate) use blog_post::get_org_files;
pub(crate) use blog_post::BlogPost; pub(crate) use blog_post::BlogPost;
pub(crate) use blog_post::get_org_files;
pub(crate) use blog_post_page::BlogPostPage; pub(crate) use blog_post_page::BlogPostPage;
pub(crate) use blog_post_page::PublishStatus;
pub(crate) use bold::IBold; pub(crate) use bold::IBold;
pub(crate) use center_block::ICenterBlock; pub(crate) use center_block::ICenterBlock;
pub(crate) use citation::ICitation; pub(crate) use citation::ICitation;
@@ -127,6 +128,7 @@ pub(crate) use regular_link::LinkTarget;
pub(crate) use section::ISection; pub(crate) use section::ISection;
pub(crate) use special_block::ISpecialBlock; pub(crate) use special_block::ISpecialBlock;
pub(crate) use src_block::ISrcBlock; pub(crate) use src_block::ISrcBlock;
pub(crate) use src_block::ISrcSegment;
pub(crate) use statistics_cookie::IStatisticsCookie; pub(crate) use statistics_cookie::IStatisticsCookie;
pub(crate) use strike_through::IStrikeThrough; pub(crate) use strike_through::IStrikeThrough;
pub(crate) use subscript::ISubscript; pub(crate) use subscript::ISubscript;

View File

@@ -21,6 +21,7 @@ use super::plain_text::IPlainText;
use super::radio_link::IRadioLink; use super::radio_link::IRadioLink;
use super::radio_target::IRadioTarget; use super::radio_target::IRadioTarget;
use super::ITarget;
use super::regular_link::IRegularLink; use super::regular_link::IRegularLink;
use super::statistics_cookie::IStatisticsCookie; use super::statistics_cookie::IStatisticsCookie;
use super::strike_through::IStrikeThrough; use super::strike_through::IStrikeThrough;
@@ -29,7 +30,6 @@ use super::superscript::ISuperscript;
use super::timestamp::ITimestamp; use super::timestamp::ITimestamp;
use super::underline::IUnderline; use super::underline::IUnderline;
use super::verbatim::IVerbatim; use super::verbatim::IVerbatim;
use super::ITarget;
use futures::future::{BoxFuture, FutureExt}; use futures::future::{BoxFuture, FutureExt};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@@ -1,10 +1,12 @@
use super::blog_post_page::get_date;
use super::blog_post_page::get_title;
use super::footnote_definition::IRealFootnoteDefinition;
use super::macros::intermediate;
use super::IDocumentElement; use super::IDocumentElement;
use super::IHeading; use super::IHeading;
use super::ISection; use super::ISection;
use super::PublishStatus;
use super::blog_post_page::get_date;
use super::blog_post_page::get_publish_status;
use super::blog_post_page::get_title;
use super::footnote_definition::IRealFootnoteDefinition;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use std::path::PathBuf; use std::path::PathBuf;
@@ -24,6 +26,8 @@ pub(crate) struct IPage {
pub(crate) children: Vec<IDocumentElement>, pub(crate) children: Vec<IDocumentElement>,
pub(crate) footnotes: Vec<IRealFootnoteDefinition>, pub(crate) footnotes: Vec<IRealFootnoteDefinition>,
pub(crate) natter_publish: PublishStatus,
} }
intermediate!( intermediate!(
@@ -69,6 +73,7 @@ intermediate!(
date: get_date(original.document), date: get_date(original.document),
children, children,
footnotes, footnotes,
natter_publish: get_publish_status(original.document).unwrap_or_default(),
}) })
} }
); );

View File

@@ -1,5 +1,5 @@
use super::macros::intermediate;
use super::IObject; use super::IObject;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;
@@ -31,6 +31,7 @@ intermediate!(
); );
impl IParagraph { impl IParagraph {
#[allow(clippy::needless_lifetimes)]
pub(crate) async fn artificial<'orig, 'parse>( pub(crate) async fn artificial<'orig, 'parse>(
_intermediate_context: crate::intermediate::IntermediateContext<'orig, 'parse>, _intermediate_context: crate::intermediate::IntermediateContext<'orig, 'parse>,
children: Vec<IObject>, children: Vec<IObject>,
@@ -41,4 +42,32 @@ impl IParagraph {
post_blank, post_blank,
}) })
} }
/// Checks if the paragraph contains nothing but a single image.
///
/// When this happens, we want to center the image.
pub(crate) fn is_single_image(&self) -> bool {
let num_images = self
.children
.iter()
.filter(|c| match c {
IObject::RegularLink(iregular_link) => matches!(
&iregular_link.target,
super::LinkTarget::Image { src: _, alt: _ }
),
_ => false,
})
.count();
num_images == 1
&& self.children.iter().all(|c| match c {
IObject::RegularLink(iregular_link) => matches!(
&iregular_link.target,
super::LinkTarget::Image { src: _, alt: _ }
),
IObject::PlainText(iplain_text) => {
iplain_text.source.chars().all(|c| c.is_ascii_whitespace())
}
_ => false,
})
}
} }

View File

@@ -1,5 +1,5 @@
use super::macros::intermediate;
use super::IPlainListItem; use super::IPlainListItem;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;

View File

@@ -1,5 +1,5 @@
use super::macros::intermediate;
use super::IPlainListSimpleItem; use super::IPlainListSimpleItem;
use super::macros::intermediate;
use super::IElement; use super::IElement;
use super::IObject; use super::IObject;

View File

@@ -1,5 +1,5 @@
use super::macros::intermediate;
use super::IObject; use super::IObject;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;

View File

@@ -1,5 +1,5 @@
use super::macros::intermediate;
use super::IElement; use super::IElement;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;

View File

@@ -3,11 +3,11 @@ use organic::types::Element;
use organic::types::Object; use organic::types::Object;
use std::collections::HashMap; use std::collections::HashMap;
use super::ast_node::IAstNode;
use super::ast_node::IntoIAstNode;
use super::IObject; use super::IObject;
use super::IParagraph; use super::IParagraph;
use super::IntermediateContext; use super::IntermediateContext;
use super::ast_node::IAstNode;
use super::ast_node::IntoIAstNode;
type IdCounter = u16; type IdCounter = u16;
@@ -180,6 +180,7 @@ async fn convert_definition_contents<'orig, 'parse>(
} }
/// Take a footnote definition that has not yet received a reference and move it into the active footnotes. /// Take a footnote definition that has not yet received a reference and move it into the active footnotes.
#[allow(clippy::needless_lifetimes)]
pub(crate) async fn promote_footnote_definition<'orig, 'parse>( pub(crate) async fn promote_footnote_definition<'orig, 'parse>(
intermediate_context: IntermediateContext<'orig, 'parse>, intermediate_context: IntermediateContext<'orig, 'parse>,
label: &'parse str, label: &'parse str,

View File

@@ -5,9 +5,9 @@ use organic::types::LinkType;
use organic::types::StandardProperties; use organic::types::StandardProperties;
use url::Url; use url::Url;
use super::IntermediateContext;
use super::get_web_path; use super::get_web_path;
use super::macros::intermediate; use super::macros::intermediate;
use super::IntermediateContext;
use super::IObject; use super::IObject;
use crate::context::RenderContext; use crate::context::RenderContext;
@@ -73,7 +73,8 @@ impl LinkTarget {
) -> Result<LinkTarget, CustomError> { ) -> Result<LinkTarget, CustomError> {
// If link type is file and the path ends in .svg then make it an image target // If link type is file and the path ends in .svg then make it an image target
if let LinkType::File = link_type if let LinkType::File = link_type
&& input.to_ascii_lowercase().ends_with(".svg") && (input.to_ascii_lowercase().ends_with(".svg")
|| input.to_ascii_lowercase().ends_with(".png"))
{ {
let src = Self::get_image_src(&input)?; let src = Self::get_image_src(&input)?;
let alt = Self::get_image_alt(&input)?; let alt = Self::get_image_alt(&input)?;
@@ -159,7 +160,7 @@ impl LinkTarget {
render_context.output_file, render_context.output_file,
relative_path_to_file, relative_path_to_file,
)?; )?;
let path_to_file = render_context render_context
.dependency_manager .dependency_manager
.lock() .lock()
.unwrap() .unwrap()
@@ -185,7 +186,7 @@ impl LinkTarget {
if path.is_absolute() { if path.is_absolute() {
return Ok(format!("file://{}", input)); return Ok(format!("file://{}", input));
} }
return Ok(input.into_owned()); Ok(input.into_owned())
} }
/// Get file name from the last segment of an image path. /// Get file name from the last segment of an image path.
@@ -198,7 +199,7 @@ impl LinkTarget {
let path = Path::new(input.as_ref()); let path = Path::new(input.as_ref());
match path match path
.components() .components()
.last() .next_back()
.ok_or("Images should have at least one component in their path.")? .ok_or("Images should have at least one component in their path.")?
{ {
std::path::Component::Prefix(_) => { std::path::Component::Prefix(_) => {
@@ -208,9 +209,7 @@ impl LinkTarget {
std::path::Component::RootDir std::path::Component::RootDir
| std::path::Component::CurDir | std::path::Component::CurDir
| std::path::Component::ParentDir => { | std::path::Component::ParentDir => {
return Err( Err("Final component of an image path should be a normal component.".into())
"Final component of an image path should be a normal component.".into(),
);
} }
std::path::Component::Normal(file_name) => Ok(file_name std::path::Component::Normal(file_name) => Ok(file_name
.to_str() .to_str()
@@ -235,6 +234,7 @@ mod tests {
let registry = Registry::new(); let registry = Registry::new();
let registry = Arc::new(Mutex::new(registry)); let registry = Arc::new(Mutex::new(registry));
let intermediate_context = IntermediateContext::new(registry)?; let intermediate_context = IntermediateContext::new(registry)?;
#[allow(clippy::single_element_loop)]
for (inp, typ) in [( for (inp, typ) in [(
"https://test.example/foo", "https://test.example/foo",
LinkType::Protocol(Cow::from("https")), LinkType::Protocol(Cow::from("https")),

View File

@@ -1,5 +1,5 @@
use super::macros::intermediate;
use super::IElement; use super::IElement;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;

View File

@@ -1,14 +1,31 @@
use std::borrow::Borrow;
use super::macros::intermediate; use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;
use tree_sitter_highlight::HighlightConfiguration;
use tree_sitter_highlight::HighlightEvent;
use tree_sitter_highlight::Highlighter;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct ISrcBlock { pub(crate) struct ISrcBlock {
pub(crate) lines: Vec<String>, pub(crate) lines: Vec<ISrcLine>,
pub(crate) language: Option<String>, pub(crate) language: Option<String>,
pub(crate) post_blank: organic::types::PostBlank, pub(crate) post_blank: organic::types::PostBlank,
} }
#[derive(Debug, Clone)]
pub(crate) struct ISrcLine {
pub(crate) children: Vec<ISrcSegment>,
}
#[derive(Debug, Clone)]
pub(crate) enum ISrcSegment {
RawText(String),
HighlightStart { name: String },
HighlightEnd,
}
intermediate!( intermediate!(
ISrcBlock, ISrcBlock,
&'orig organic::types::SrcBlock<'parse>, &'orig organic::types::SrcBlock<'parse>,
@@ -59,14 +76,66 @@ intermediate!(
}) })
.collect(); .collect();
let language = original.language.map(str::to_owned); let language = original.language.map(str::to_owned);
match language.as_deref() {
Some(lang @ "bash") => {
let highlighted = highlight_bash(&lines);
if let Ok(highlighted) = highlighted {
return Ok(ISrcBlock {
lines: highlighted,
language,
post_blank: original.get_post_blank(),
});
} else {
println!("Warning: Failed to highlight {} source.", lang);
}
}
Some(lang @ "nix") => {
let highlighted = highlight_nix(&lines);
if let Ok(highlighted) = highlighted {
return Ok(ISrcBlock {
lines: highlighted,
language,
post_blank: original.get_post_blank(),
});
} else {
println!("Warning: Failed to highlight {} source.", lang);
}
}
Some(lang @ "python") => {
let highlighted = highlight_python(&lines);
if let Ok(highlighted) = highlighted {
return Ok(ISrcBlock {
lines: highlighted,
language,
post_blank: original.get_post_blank(),
});
} else {
println!("Warning: Failed to highlight {} source.", lang);
}
}
Some(lang) => {
println!("Warning: No highlighting for language: {}", lang);
}
_ => {}
};
let highlighted = highlight_plain(&lines)?;
Ok(ISrcBlock { Ok(ISrcBlock {
lines, lines: highlighted,
language, language,
post_blank: original.get_post_blank(), post_blank: original.get_post_blank(),
}) })
} }
); );
impl ISrcLine {
pub(crate) fn new() -> ISrcLine {
ISrcLine {
children: Vec::new(),
}
}
}
fn ascii_whitespace_value(c: char) -> usize { fn ascii_whitespace_value(c: char) -> usize {
match c { match c {
' ' => 1, ' ' => 1,
@@ -76,3 +145,161 @@ fn ascii_whitespace_value(c: char) -> usize {
_ => unreachable!("Only ascii whitespace can reach this code."), _ => unreachable!("Only ascii whitespace can reach this code."),
} }
} }
fn highlight_plain<L>(lines: &[L]) -> Result<Vec<ISrcLine>, CustomError>
where
std::string::String: for<'a> From<&'a L>,
{
Ok(lines
.iter()
.map(|l| {
let mut line = ISrcLine::new();
line.children.push(ISrcSegment::RawText(l.into()));
line
})
.collect())
}
fn highlight_tree_sitter<L>(
config: HighlightConfiguration,
highlight_names: &[&str],
lines: &[L],
) -> Result<Vec<ISrcLine>, CustomError>
where
L: Borrow<str>,
{
let combined_text = lines.join("");
// Need 1 highlighter per thread
let mut highlighter = Highlighter::new();
let highlights = highlighter
.highlight(&config, combined_text.as_bytes(), None, |_| None)
.unwrap();
let mut highlighted_text: Vec<ISrcLine> = Vec::with_capacity(lines.len());
let mut current_line = ISrcLine::new();
let mut highlight_stack: Vec<&str> = Vec::new();
for event in highlights {
match event.unwrap() {
HighlightEvent::Source { start, end } => {
let mut span = &combined_text[start..end];
while let Some(line_break_index) = span.find('\n') {
let first_line = &span[..(line_break_index + 1)];
current_line
.children
.push(ISrcSegment::RawText(first_line.to_owned()));
current_line.children.extend(
highlight_stack
.iter()
.map(|_name| ISrcSegment::HighlightEnd),
);
highlighted_text.push(current_line);
current_line = ISrcLine::new();
current_line
.children
.extend(
highlight_stack
.iter()
.map(|name| ISrcSegment::HighlightStart {
name: (*name).into(),
}),
);
span = &span[(line_break_index + 1)..];
}
if !span.is_empty() {
current_line
.children
.push(ISrcSegment::RawText(span.to_owned()));
}
}
HighlightEvent::HighlightStart(s) => {
highlight_stack.push(highlight_names[s.0]);
current_line.children.push(ISrcSegment::HighlightStart {
name: highlight_names[s.0].into(),
});
}
HighlightEvent::HighlightEnd => {
highlight_stack.pop();
current_line.children.push(ISrcSegment::HighlightEnd);
}
}
}
debug_assert!(highlight_stack.is_empty());
Ok(highlighted_text)
}
fn highlight_bash<L>(lines: &[L]) -> Result<Vec<ISrcLine>, CustomError>
where
L: Borrow<str>,
{
let highlight_names = ["comment", "function", "keyword", "property", "string"];
let language = tree_sitter_bash::LANGUAGE.into();
let mut config =
HighlightConfiguration::new(language, "bash", tree_sitter_bash::HIGHLIGHT_QUERY, "", "")
.unwrap();
config.configure(&highlight_names);
highlight_tree_sitter(config, &highlight_names, lines)
}
fn highlight_nix<L>(lines: &[L]) -> Result<Vec<ISrcLine>, CustomError>
where
L: Borrow<str>,
{
let highlight_names = [
"comment",
"keyword",
"property",
"string",
"string.special.path",
// "string.special.uri",
];
let language = tree_sitter_nix::LANGUAGE.into();
let mut config =
HighlightConfiguration::new(language, "nix", tree_sitter_nix::HIGHLIGHTS_QUERY, "", "")
.unwrap();
config.configure(&highlight_names);
highlight_tree_sitter(config, &highlight_names, lines)
}
fn highlight_python<L>(lines: &[L]) -> Result<Vec<ISrcLine>, CustomError>
where
L: Borrow<str>,
{
let highlight_names = [
"comment",
"function.builtin",
"keyword",
"property",
"string",
"type",
"variable",
];
let language = tree_sitter_python::LANGUAGE.into();
let mut config = HighlightConfiguration::new(
language,
"python",
tree_sitter_python::HIGHLIGHTS_QUERY,
"",
"",
)
.unwrap();
config.configure(&highlight_names);
highlight_tree_sitter(config, &highlight_names, lines)
}
// use tree_sitter::Parser;
// fn dump_nix<B>(body: B) -> Result<(), CustomError>
// where
// B: AsRef<str>,
// {
// let mut parser = Parser::new();
// parser
// .set_language(&tree_sitter_nix::LANGUAGE.into())
// .expect("Error loading Nix grammar");
// let mut tree = parser.parse(body.as_ref(), None).unwrap();
// println!("{}", tree.root_node());
// Ok(())
// }

View File

@@ -1,7 +1,7 @@
use organic::types::StandardProperties; use organic::types::StandardProperties;
use super::macros::intermediate;
use super::IObject; use super::IObject;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;

View File

@@ -1,5 +1,23 @@
use super::macros::inoop; use super::macros::intermediate;
use super::util::coalesce_whitespace;
use crate::error::CustomError; use crate::error::CustomError;
use organic::types::StandardProperties; use organic::types::StandardProperties;
inoop!(ITimestamp, Timestamp); #[derive(Debug, Clone)]
pub(crate) struct ITimestamp {
pub(crate) source: String,
pub(crate) post_blank: organic::types::PostBlank,
}
intermediate!(
ITimestamp,
&'orig organic::types::Timestamp<'parse>,
original,
_intermediate_context,
{
Ok(ITimestamp {
source: coalesce_whitespace(original.source).into_owned(),
post_blank: original.get_post_blank(),
})
}
);

View File

@@ -1,7 +1,7 @@
use organic::types::StandardProperties; use organic::types::StandardProperties;
use super::macros::intermediate;
use super::IObject; use super::IObject;
use super::macros::intermediate;
use crate::error::CustomError; use crate::error::CustomError;

View File

@@ -1,4 +1,3 @@
#![feature(let_chains)]
use std::process::ExitCode; use std::process::ExitCode;
use clap::Parser; use clap::Parser;
@@ -8,11 +7,14 @@ use self::cli::parameters::Commands;
use self::command::build::build_site; use self::command::build::build_site;
use self::command::init::init_natter_folder; use self::command::init::init_natter_folder;
use self::error::CustomError; use self::error::CustomError;
use self::init_tracing::init_telemetry;
use self::init_tracing::shutdown_telemetry;
mod cli; mod cli;
mod command; mod command;
mod config; mod config;
mod context; mod context;
mod error; mod error;
mod init_tracing;
mod intermediate; mod intermediate;
mod render; mod render;
mod walk_fs; mod walk_fs;
@@ -23,6 +25,7 @@ fn main() -> Result<ExitCode, CustomError> {
} }
async fn main_body() -> Result<ExitCode, CustomError> { async fn main_body() -> Result<ExitCode, CustomError> {
init_telemetry().expect("Telemetry should initialize successfully.");
let args = Cli::parse(); let args = Cli::parse();
match args.command { match args.command {
Commands::Init(args) => { Commands::Init(args) => {
@@ -32,5 +35,6 @@ async fn main_body() -> Result<ExitCode, CustomError> {
build_site(args).await?; build_site(args).await?;
} }
}; };
shutdown_telemetry().expect("Telemetry should shutdown successfully.");
Ok(ExitCode::SUCCESS) Ok(ExitCode::SUCCESS)
} }