9 Commits
main ... main

13 changed files with 378 additions and 39 deletions

View File

@ -0,0 +1,43 @@
.bold {
font-weight: bold;
}
.italic {
font-weight: italic;
}
.dimgray { color: #525252; }
.red { color: #ee5396; }
.blue { color: #42be65; }
.yellow { color: #ff7eb6; }
.green { color: #33b1ff; }
.purple { color: #be95ff; }
.cyan { color: #3ddbd9; }
.white { color: #08bdba; }
.highlighted .dimgray { color: #525252; }
.highlighted .red { color: #98a9ff; }
.highlighted .blue { color: #262626; }
.highlighted .yellow { color: #393939; }
.highlighted .green { color: #dde1e6; }
.highlighted .purple { color: #ffffff; }
.highlighted .cyan { color: #82cfff; }
.highlighted .white { color: #f2f4f8; }
.bg-dimgray { color: #525252; }
.bg-red { color: #ee5396; }
.bg-blue { color: #42be65; }
.bg-yellow { color: #ff7eb6; }
.bg-green { color: #33b1ff; }
.bg-purple { color: #be95ff; }
.bg-cyan { color: #3ddbd9; }
.bg-white { color: #08bdba; }
.highlighted .bg-dimgray { color: #525252; }
.highlighted .bg-red { color: #98a9ff; }
.highlighted .bg-blue { color: #262626; }
.highlighted .bg-yellow { color: #393939; }
.highlighted .bg-green { color: #dde1e6; }
.highlighted .bg-purple { color: #ffffff; }
.highlighted .bg-cyan { color: #82cfff; }
.highlighted .bg-white { color: #f2f4f8; }

View File

@ -25,11 +25,14 @@
(getattr (stat file) "st_ctime"))
(defn last-changed-prev-compile [file]
(getattr (stat (-> file (.replace ".hy" ".html") (.replace "./pages/" "../site/html/"))) "st_ctime"))
(if (file? (-> file (.replace ".hy" ".html") (.replace "./pages/" "../site/html/")))
(getattr (stat (-> file (.replace ".hy" ".html") (.replace "./pages/" "../site/html/"))) "st_ctime")
False))
(defmain []
(setv last-compile-time (last-changed "../site"))
(setv data-dir-changed? (> (last-changed "data") last-compile-time))
(setv assets-dir-changed? (> (last-changed "assets") last-compile-time))
(setv last-commit-newer (> (-> (check-output #[[git log -1 --format="%at"]] :shell True) (.decode) (.strip) (int)) last-compile-time))
(print f"last compiled: {last-compile-time :.0f}")
@ -37,10 +40,21 @@
(when (not (dir? "output"))
(mkdir "output"))
(for [path (glob "./pages/**/*" :recursive True)]
(when (in "__pycache__" path)
(continue))
;; only recompile pages when:
;; - the source page is newer than the output
;; - the `src/data` directory is newer than the output
;; - the latest commit is newer than the output
;; - the page is view-thought.hy and there are thoughts being recompiled
(setv pages-to-build (lfor path (glob "./pages/**/*" :recursive True)
:if (not-in "__pycache__" path)
:if (or (> (last-changed path) (last-changed-prev-compile path)) data-dir-changed? assets-dir-changed? last-commit-newer)
path))
(when (any (map (fn [x] (in "/thoughts/" x)) pages-to-build))
(when (all (map (fn [x] (not-in "./pages/html/view-thought.hy" x)) pages-to-build))
(setv pages-to-build ["./pages/html/view-thought.hy" #* pages-to-build])))
(for [path pages-to-build]
(cond
(and (dir? path) (not (dir? (.replace path "pages" "output"))))
(do
@ -48,21 +62,13 @@
(mkdir (.replace path "pages" "output")))
(file? path)
;; only recompile pages when:
;; - the source page is newer than the output
;; - the `src/data` directory is newer than the output
;; - the latest commit is newer than the output
(if (or (> (last-changed path) (last-changed-prev-compile path)) data-dir-changed? last-commit-newer)
(do
(print f"building {path}")
(setv page-name (.split (cut path 2 -3) "/"))
(enshrine page-name)
(with [target (open (+ "./output/" (.join "/" (cut page-name 1 None)) ".html") "w")]
(.write target (form->html
(hy-eval (hy.read (with [source (open path "r")]
(.read source)))
:locals local-scope
:globals global-scope)))))
(print f"{(.replace path #[[./pages/]] #[[../site/html/]])} more recent, skipping")))))
(do
(print f"building {path}")
(setv page-name (.split (cut path 2 -3) "/"))
(enshrine page-name)
(with [target (open (+ "./output/" (.join "/" (cut page-name 1 None)) ".html") "w")]
(.write target (form->html
(hy-eval (hy.read (with [source (open path "r")]
(.read source)))
:locals local-scope
:globals global-scope))))))))

View File

@ -64,10 +64,15 @@ specific:
src: //tasiaiso.vulpecula.zone/images/badges/tasiaiso.png
alt: tasia's 88x31
# does not 301 to https if requested with http
- host: //www.31a05b.net/
src: https://www.31a05b.net/a/8831/31a05b.png
alt: 31A05B9C's random site
- host: //bunbun.dev/
src: //bunbun.dev/assets/88x31s/bunbun.dev.gif
alt: bunbun.dev
general:
- src: assets/88x31/e2vial-88x31.gif
alt: an estrogen vial spinning next to the text "powered by estrogen"

View File

@ -0,0 +1,8 @@
(defmacro inherit [#* names]
(lfor name names
`(setx ~name (get local-scope (str (get '(~name) 0))))))
(defmacro enshrine [#* names]
(lfor name names
`(setv (get local-scope (str (get '(~name) 0))) ~name)))

View File

@ -0,0 +1,12 @@
<pre><span class="bold ">Benchmark </span><span class="bold ">1</span>: curl https://natalieee.net/html/view-thought.html
Time (<span class="bold green ">mean</span> ± <span class="green ">σ</span>): <span class="bold green "> 1.197 s</span> ± <span class="green "> 0.017 s</span> [User: <span class="blue ">0.021 s</span>, System: <span class="blue ">0.013 s</span>]
Range (<span class="cyan ">min</span> … <span class="purple ">max</span>): <span class="cyan "> 1.163 s</span> … <span class="purple "> 1.319 s</span> 100 runs
<span class="bold ">Benchmark </span><span class="bold ">2</span>: curl http://test-of.v2.natalieee.net/html/view-thought.html
Time (<span class="bold green ">mean</span> ± <span class="green ">σ</span>): <span class="bold green ">176.2 ms</span> ± <span class="green "> 5.1 ms</span> [User: <span class="blue ">20.9 ms</span>, System: <span class="blue ">12.2 ms</span>]
Range (<span class="cyan ">min</span> … <span class="purple ">max</span>): <span class="cyan ">162.2 ms</span> … <span class="purple ">210.5 ms</span> 100 runs
<span class="bold ">Summary</span>
<span class="cyan ">curl http://test-of.v2.natalieee.net/html/view-thought.html</span> ran
<span class="bold green "> 6.79</span> ± <span class="green ">0.22</span> times faster than <span class="purple ">curl https://natalieee.net/html/view-thought.html</span>
</pre>

View File

@ -0,0 +1,12 @@
<pre><span class="bold ">Benchmark </span><span class="bold ">1</span>: curl https://natalieee.net/html/view-thought.html?filter-category=python
Time (<span class="bold green ">mean</span> ± <span class="green ">σ</span>): <span class="bold green "> 1.083 s</span> ± <span class="green "> 0.031 s</span> [User: <span class="blue ">0.022 s</span>, System: <span class="blue ">0.012 s</span>]
Range (<span class="cyan ">min</span> … <span class="purple ">max</span>): <span class="cyan "> 1.053 s</span> … <span class="purple "> 1.305 s</span> 100 runs
<span class="bold ">Benchmark </span><span class="bold ">2</span>: curl http://test-of.v2.natalieee.net/html/view-thought.html?filter-tag=python
Time (<span class="bold green ">mean</span> ± <span class="green ">σ</span>): <span class="bold green ">176.5 ms</span> ± <span class="green "> 5.6 ms</span> [User: <span class="blue ">20.7 ms</span>, System: <span class="blue ">12.1 ms</span>]
Range (<span class="cyan ">min</span> … <span class="purple ">max</span>): <span class="cyan ">160.8 ms</span> … <span class="purple ">206.0 ms</span> 100 runs
<span class="bold ">Summary</span>
<span class="cyan ">curl http://test-of.v2.natalieee.net/html/view-thought.html?filter-tag=python</span> ran
<span class="bold green "> 6.14</span> ± <span class="green ">0.26</span> times faster than <span class="purple ">curl https://natalieee.net/html/view-thought.html?filter-category=python</span>
</pre>

View File

@ -0,0 +1,12 @@
<pre><span class="bold ">Benchmark </span><span class="bold ">1</span>: curl https://natalieee.net/html/view-thought.html?thought=dollcode
Time (<span class="bold green ">mean</span> ± <span class="green ">σ</span>): <span class="bold green "> 1.190 s</span> ± <span class="green "> 0.036 s</span> [User: <span class="blue ">0.021 s</span>, System: <span class="blue ">0.013 s</span>]
Range (<span class="cyan ">min</span> … <span class="purple ">max</span>): <span class="cyan "> 1.141 s</span> … <span class="purple "> 1.412 s</span> 100 runs
<span class="bold ">Benchmark </span><span class="bold ">2</span>: curl http://test-of.v2.natalieee.net/html/view-thought.html?thought=dollcode
Time (<span class="bold green ">mean</span> ± <span class="green ">σ</span>): <span class="bold green ">504.0 ms</span> ± <span class="green "> 12.6 ms</span> [User: <span class="blue ">20.6 ms</span>, System: <span class="blue ">13.3 ms</span>]
Range (<span class="cyan ">min</span> … <span class="purple ">max</span>): <span class="cyan ">480.3 ms</span> … <span class="purple ">554.3 ms</span> 100 runs
<span class="bold ">Summary</span>
<span class="cyan ">curl http://test-of.v2.natalieee.net/html/view-thought.html?thought=dollcode</span> ran
<span class="bold green "> 2.36</span> ± <span class="green ">0.09</span> times faster than <span class="purple ">curl https://natalieee.net/html/view-thought.html?thought=dollcode</span>
</pre>

View File

@ -15,7 +15,7 @@
(h2 "site info" ~(run "make-footnote \"as of last build\""))
(details
(summary "git changelog")
(pre ~(run "git log --pretty=format:'%ad %s' --date=short")))
(pre ~(run #[[git log --pretty=format:'%ad %s' --date=short | sed 's/</\&lt;/g; s/>/\&gt;/g']])))
(details
(summary "file tree of the site on the server")

View File

@ -0,0 +1,235 @@
(thought
:title "natalieee.net version 2"
:date "2025-05-14 22:45:34"
:tags ["meta" "programming"]
:description "yet another site redesign"
:content `(
(p
"it has rewritten its website again. "
#[[there was no particular reason for its rewriting of its website, it simply decided it was bored (read: needed a way to procrastinate calculus homework. again.]]
~(run #[[make-footnote '100% of site redesigns have occured while taking a calculus class.']]) ") "
#[[fortunately, it does think this new iteration of the website is better.]])
(br)
(h2 "major changes")
(hr)
(ul
(li "the site is one repository. this makes everything better.")
(li "the static site generator and web server have been rewritten in hy"))
(p #[[
both of the above have major benefits associated with them. firstly, the site is only one repository instead of the prior&mdash;and rather ridiculous&mdash;three.
this makes managing things less horrible as now, when updating something on this site, it only has to push to one repository. one is, on average, less than a range of 1-3 repositories.
additionally, this allows the changelog of the website to be autogenerated via git log. this is quite nice as it is better at being descriptive with commit messages than
it is with changelog entries for some reason.]])
(p #[[
secondly, rewriting the static site generator in hy has been quite helpful, as this one is much more familiar with hy than it was with common lisp.
this site now being written in a lisp that runs on the python interpreter is also very nice, because it means this one can invoke arbitrary arcanities of python
at the same time as it makes a website. it is very happy about this, because using python in obscure and unintended ways is very fun.]])
(p #[[
because http servers are trivial, and because the prior http server was already written in python, there are no significant improvements over the old one.
the http server's codebase is much simpler now, however, as this one removed features it deemed unecessary, and wrote it in a way that adheres more closely
to functional programming ideals than the old server's codebase.]])
(br)
(h2 "implications")
(hr)
(p #[[
it is not going to bother porting many of the blog posts from the old version of its website. rewriting said posts takes a reasonable amount of time,
as now they are written in lisp&mdash;similar to the rest of the site&mdash;instead of simply being fragments of html documents. it will port only the posts
that it deems particularly interesting to the new site.]])
(br)
(h2 "blog performance improvements")
(hr)
(p #[[
despite this page (/html/view-thought.html) still being rendered on the server at request time via bash code (as with the old version of this website),
it is now much faster. there are a variety of reasons for this: firstly, we now only execute 7 shell scripts to render this page, as opposed
to the prior iteration's 14 shell scripts. secondly, useage of awk has been minimized. logic that prior to now was done via awk is now done
in bash. this is notable because echo is significantly faster than awk. additionally, thoughts are now indexed via a "database"
(text file that gets grepped through), which is substantially faster than listing the thoughts directory and parsing metadata by reading each thought.]])
(p #[[
these changes result in a significant speed improvement for the new site:]] ~(run #[[make-footnote "benchmarks were run on-server to prevent inconsistencies in network request resolution times."]]))
(figure
(figcaption "difference (no url params)")
(code ~(run #[[cat data/thoughts/natalieee.net-v2/view-thought-diff]])))
(figure
(figcaption "difference (rendering specific thought)")
(code ~(run #[[cat data/thoughts/natalieee.net-v2/view-thought-diff-thought]])))
(figure
(figcaption "difference (filtering for tags)")
(code ~(run #[[cat data/thoughts/natalieee.net-v2/view-thought-diff-tag]])))
(br)
(h2 "detailed explanation of performance improvements")
(hr)
(p #[[
to understand the reasons behind these speed increases, we must examine the code for the view-thought page.]])
(figure
(figcaption "www/src/pages/html/view-thought.hy")
(code ~(run "syntax-hl pages/html/view-thought.hy | sed -e 's/\\[/\\&#91;/g' -e 's/\\]/\\&#93;/g'")))
(p #[[
this code is quite complicated, and unfortunately it was written by this one, meaning it is probably cognitohazardous. sorry. it would like
to note that it is not legally, ethically, or monetarily responsible for any health issues that may arrise from observation of this code.]])
(p "
the reasons that the contents of this file must be enclosed in a do block are rather arcane." ~(run (+ "make-footnote \"because instead of importing pages with python's import machinery "
"we simply read them and evaluate them with hy's internal tools, each page, combined with code generating output for that page functionally "
"just gets appended to \\`build.hy\\`. thus, view-thought must define its functions inside of a do block, such that all of them are accessible "
"to the external scope. it is necessary for them to be accessible to the external scope because thought posts rely on the \\`thought\\` "
"function, which is defined in view-thought.\"")))
(br)
(h3 "the `thought` function")
(p "
it makes the most sense to start with an explanation of `thought` function, as its properties form the basis upon which the functionality of other systems in this
file are predicated. understanding the `thought` function requires that we first understand the relevant macros:")
(figure
(figcaption "the relevant macros")
(code ~(run #[[syntax-hl data/thoughts/natalieee.net-v2/macros.hy]])))
(p "
the macro `enshrine` takes a list of variable names defined in the scope in which it is called, and saves it to a special predetermined scope that will be accessible
to the hy code in each page via the `inherit` macro. the `inherit` macro simply retreives a list of variable names that are expected to be in the afforementioned special scope and, provided
they are indeed defined there, defines them in the currenet scope. this is managed by passing the special scope as arguments in the call to `hy.compiler.hy-eval`,
which is responsible for executing the code in each hy file within the `www/src/pages/` directory.")
(p "
in `build.hy`, a list of all hy files in `www/src/pages` is enumerated such that the variable `page-name` contains an array of the relative path of each page, split by '/'.
prior to evaluating each page, we call `enshrine` on `page-name`. normally, this is irrelevant, as the value is not inherited in the page being evaluated. with pages
that call the `thought` function however, we explicitly `inherit` `page-name`, making it accessible within the scope. one should take note of the
fact that the value of `page-name` becomes associated with `page-name`'s value in `build.hy` at the time `thought` is called, not at the time it is defined."
~(run (+
"make-footnote 'this makes `thought` behave as though it also takes an argument containing the path of the current file being parsed. "
"because each page is evaluated in `build.hy` as though it is just a form, and not a function called with arguments, "
"we must use this this hacky workaround.'")))
(p "
within the `thought` function, we use the `page-name` variable to check if the thought being compiled is already in `www/data/thought-registry`, which stores the metadata
associated with each post in a tsv with the following columns: tags, file-name of the post (determined via `(get page-name -1)`), title, date, and description."
~(run #[[make-footnote "at some point, this should probably be changed to check that the thought's source file hasn't changed since \`thought-registry\` was last written."]])
" if the thought's file name is not present within the `thought-registry` file, the `thought-registry` file is appended to with metadata regarding the thought in the appropriate format.
said metadata (other than the file name) is defined in the arguments to `thought`")
(p "
when we are done updating the `thought-registry` (should it need to be updated), we simply clear the footnote counter (it would be silly to have all posts share one set of footnotes),
and return a quoted expression containing the content of the thought, an instruction to render the footnotes belonging to the thought, and the comments template with a path specific
to the thought. this return value gets evaluated in `build.hy`, where it will output a fragment of an html document to be included within `view-thought.html` as necessary based on url params.")
(br)
(dl
(dt "but natalie, shouldn't `thought` be defined in `www/src/templates/`?")
(dd "yes. it did this instead because it was bored, though."))
(h3 "bash macros")
(p #[[
prior to the `thought` function, a variety of functions and variables are defined. these can reasonably be thought of compile-time macros for bash code that will be generated in
the html output, to be evaluated at page request time. the comparison to macros is appropriate despite these simply being python f-strings, as we are functionally putting arbitrary
semantic constructs through a preprocessor, which outputs bash. it is by this mechanism that a suitable abstraction is created for writing efficient(ish) bash code for server side rendering.]])
(p #[[
the afforementioned &quot;bash macros&quot; are not used outside of this file. in fact, the only place they are used is at the end of
this file, where we call the page template. prior to making sense of what on earth is going on there, we must understand what some of these
macros do: to do so, a distinction between those defined via `defn` and those defined via `setv` should be established:]])
(br)
(dl
(dt "macros defined via `defn`")
(dd "
macros which take an argument or arguments, manifesting as hy forms. a good example being `subshell`, which takes arbitrarily many hy strings
(which should be bash expressions) and outputs bash code consisting of all of those strings within a subshell and delineated by ';'.")
(dt "macros defined via `setv`")
(dd "
these take no arguments, but often include references to bash variables that they assume will be defined within the scope in which they are inserted.
they are used as a means of expanding formats for bash variables to be rendered in. a good example of this is `thought-tag-format`,
which expands to simply be a link to `?filter-tag=${tag}` with the link text `${tag}`."))
(p #[[note that for the route `/html/view-thought.html`, any url parameter encoded in the requests to this page
will be propogated to the html the server returns, such that any instances of `{param-name}` in the html document&mdash;assuming that the route contains
&quot;?param-name=param-value&quot;&mdash;will be replaced with `param-value`. this will occur prior to the server evaluating bash commands in said document. such a system,
to give an example, allows for a page at some route that expects an optional `option` parameter, which may be either `1` or `2`. since the value of
`{option}` in the corresponding html for that route is substituted prior to bash commands being executed, this allows for conditional logic in bash scripts embedded in the html file corresponding to that route
(eg: `test {option} = 1 && echo option 1 || echo option 2`).]])
(p #[[
`test` is used in the above example instead of `[ ... ]` because, as is stated within the about-site page, the way the server knows it is to execute a part of an html document
as bash code is by that string being enclosed within $&#91; ... &#93;. as such, we may only use the subset of bash code that does not include &quot;[&quot;, or &quot;]&quot;,
else we would prematurely end any bash expression containing &quot;]&quot;]])
(p "with the above context, the following macros can now be slightly more reasonably explained:")
(br)
(dl
(dt "check-param-defined")
(dd "
this macro takes a param name (intended to be the name of a url parameter possibly passed in the query to the page it is within), and, if it is passed in the url, exits with 0,
whilst otherwise exiting with 1. the way that it checks if the url parameter is defined is by checking that `echo '{{' '{param-name}' '}}' | sed 's/ //g'`
(which will evaluate to &quot;{param-name}&quot; without being substituted, even if a parameter of that name is in the url) is not equal to `{param-name}` (which will get substituted in the
case that a parameter of that name is in the url).")
(dt "if-param-then-else")
(dd "
takes url param name `param-name`, bash expression `param-defined`, and bash expression `otherwise`, as well as optional boolean `echo`, which defaults to true.
if `param-name` is defined, the `param-defined` expression is executed, otherwise, the `otherwise` expression is executed. if `echo` is true, expressions are executed as
`echo expression`, otherwise, they are executed simply as `expression`.")
(dt "param-if-param-else")
(dd "
takes url param name `param-name` and expression `otherwise`. calls `if-param-then-else` with arguments `param-name`, `param-name`, and `otherwise`.
thus, it evaluates to `param-name` if `check-param-defined` of `param-name` is true, whilst otherwise evaluating to `otherwise`.")
(dt "get-thought-field")
(dd "
takes field name `field`, indexes the `thought-registry` file at the line indicated by the current value of the thought= parameter, and evaluates to the value of
the column corresponding to `field`."))
(h3 "the call to `page`")
(p "
the call to the `page` template is like every other call to the `page` template on this site. the interesting parts are all the bash code.")
(p "
to start simply, the title of the page is set to")
(code (pre ~(run "syntax-hl pages/html/view-thought.hy | pup 'pre' --pre | tail -n 32 | head -n1 | sed -e 's/\\[/\\&#91;/g' -e 's/\\]/\\&#93;/g' -e 's/<span class=\"hySexp\"> <\\/span>/<span class=\"hySexp\">\\&nbsp;<\\/span>/g'")))
(p "this sets the title of the page to the value of the `thought` parameter if it passed, and otherwise to the string \"natalie thoughts\"")
(p "next, the description is set to the following:")
(code (pre ~(run "syntax-hl pages/html/view-thought.hy | pup 'pre' --pre | tail -n 31 | head -n6 | sed -e 's/\\[/\\&#91;/g' -e 's/\\]/\\&#93;/g' -e 's/<span class=\"hySexp\"> <\\/span>/<span class=\"hySexp\">\\&nbsp;<\\/span>/g'")))
(p "this sets the page's description to the value of the description column in the `thought-registry` file for the relevant thought if the `thought` parameter is passed. if the `thought` parameter is not passed,
the description gets set to 'thoughts filtered for &quot;{filter-tag}&quot;' if the `filter-tag` paremeter is passed, and otherwise &quot;natalie thoughts&quot;")
(p "finally, we have the complicated part:")
(code (pre ~(run "syntax-hl pages/html/view-thought.hy | pup 'pre' --pre | tail -n 23 | sed -e 's/\\[/\\&#91;/g' -e 's/\\]/\\&#93;/g' -e 's/<span class=\"hySexp\"> <\\/span>/<span class=\"hySexp\">\\&nbsp;<\\/span>/g'")))
(p "the first section is very simple&mdash;it just sets the value of the heading in the same manner that the description is set. how the class of the relevant div is set is also very self-explanatory.")
(p "after setting the heading, the code then checks to see if the url param &quot;thought&quot; is defined. if it is, then it executes a variety of commands inside of a subshell:
firstly, it defines the variables `tags` and `date` to be the value of `get-thought-param-field` for the respective fields. following this, we echo `thought-display-meta-format`, which displays the afforementioned metadata&mdash;as the name would suggest.
this is executed inside of a subshell such that the values defined as variables are accessible to `thought-display-meta-format`, which expects said variables to be defined. finally, we run the `include` command (`www/src/scripts/include`) on the file
defined in the file_name column of the post's entry in the `thought-registry`. note that this fails by returning a blank page if there is no entry corresponding to the value of the &quot;thought&quot; param. it should probably fix this.")
(p "if the &quot;thought&quot; parameter is not passed, a list of posts is rendered by enumerating over lines in the `thought-registry` file. prior to said enumerating, if the &quot;filter-tag&quot; param is passed, then we grep the `thought-registry` file for lines including its
value in the list of tags. following this, we sort the lines of the file by the fourth tab-separated column (which contains the publish date) with an at least somewhat silly sort expression."
~(run #[[make-footnote '&quot;{{&quot; and &quot;}}&quot; is the syntax to include a regular &quot;{&quot; or &quot;}&quot; in a python f-string']])
" this sort expression is &quot;like that&quot; because, in compliance with POSIX or something, when sorting numerically by a certain field, sort will stop at the first non-numeric character in that field.
as such, each field in the date is specified. beyond the day, the time of a post is not sorted because it does not write that much. only after sorting do we actually enumerate over posts, for each post reading each column in `thought-registry`
in to various variables, and then echoing `thought-list-entry-format`, which&mdash;as with `thought-display-meta-format`&mdash;expects these variables to be defined.")
(br)
(h3 "performance limitations of this appoach")
(p "because we run `include` to render the html fragments containing post contents at request time, the speed a request can be served is highly limited by the amount of time it takes to run said `include` command.
it is necessary to run `include`, because the server does not evaluate $&#91; ... &#93;. expressions recursively, but only once. as such, simply sending the raw file contents of a blog post would not work,
as bash commands meant to render parts of the post would not get executed.")
(p "currently, `include` calls a hy script that runs the same code that the http server does to parse bash embedded in html. because python is slow, and we are writing and reading potentially large amounts of data to a stream in bash,
this is rather slow. since there is no way to have dynamic content in blog posts if they were only evaluated at compile time, some means of making the process of rendering a thought at request time must be found to improve the time
it takes to serve a request to this page.")))

View File

@ -1,9 +1,9 @@
(do ;; required for arcane scoping
(defn check-param-exists [param-name]
(defn check-param-defined [param-name]
#[f[test "$(echo '{{' '{param-name}' '}}' | sed 's/ //g')" != '{{{param-name}}}']f])
(defn if-param-then-else [param-name param-defined otherwise [echo True]]
#[f[ {(check-param-exists param-name)} && {(if echo "echo " "")}{param-defined} || {(if echo "echo " "")}{otherwise} ]f])
#[f[ {(check-param-defined param-name)} && {(if echo "echo " "")}{param-defined} || {(if echo "echo " "")}{otherwise} ]f])
(defn param-if-param-else [param-name otherwise]
(if-param-then-else param-name f"'{{{param-name}}}'" otherwise))
@ -18,7 +18,7 @@
`(a (:href "?filter-tag=${tag}") "${tag}")))
(setv thought-tag-list-format
#[f[tags: $(while read -r tag{";"} do echo "{thought-tag-format}"{";"} done <<< "${{tags//{#[[;]]}/$'\n'}}")]f])
#[f[tags: $(while read -r tag{";"} do echo "{thought-tag-format}"{";"} done <<< "${{tags//;/$'\n'}}")]f])
(setv thought-list-entry-format (form->html
`(div (:id "${file_name}" :class thought-list-entry)
@ -34,8 +34,9 @@
(defn thought [[title "empty title"] [description ""] [date "1970-01-01"] [tags []] [content '(p "no content provided")]]
(inherit page-name)
(with [thought-registry (open "../data/thought-registry" "a")]
(.write thought-registry f"{(.join #[[;]] tags)}\t{(get page-name -1)}\t{title}\t{date}\t{description}\n"))
(when (not-in (get page-name -1) (with [thought-registry (open "../data/thought-registry" "r")] (.read thought-registry)))
(with [thought-registry (open "../data/thought-registry" "a")]
(.write thought-registry f"{(.join #[[;]] tags)}\t{(get page-name -1)}\t{title}\t{date}\t{description}\n")))
(run "echo > /tmp/footnote_count")
@ -45,21 +46,22 @@
~(run "put-footnotes")
~(comments f"/html/thought/view-thought.html?thought={(get page-name -1)}")))
(run "echo -n > ../data/thought-registry")
;; (run "echo -n > ../data/thought-registry")
(page
:title #[f[$[{(param-if-param-else "thought" "natalie thoughts")}]]f]
:title f" $[{(param-if-param-else "thought" "natalie thoughts")}] "
:description #[f[
$[{(if-param-then-else "thought" #[f["$({(get-thought-param-field "description")})"]f] #[f[
$({(if-param-then-else
"filter-tag"
$[{(if-param-then-else "thought"
#[f["$({(get-thought-param-field "description")})"]f]
#[f[$({(if-param-then-else "filter-tag"
"'thoughts filtered for &quot;{filter-tag}&quot;'"
"an enumeration of natalie thoughts. it is functionally a blog")})]f])}]]f]
`(section
(h1 ~#[f[
$[{(if-param-then-else "thought" #[f[$({(get-thought-param-field "title")})]f] #[f[
$({(if-param-then-else
$[{(if-param-then-else "thought"
#[f[$({(get-thought-param-field "title")})]f]
#[f[$({(if-param-then-else
"filter-tag"
"'thoughts filtered for &quot;{filter-tag}&quot;'"
"natalie thoughts. functionally a blog")})]f])}]]f])
@ -75,6 +77,6 @@
#[f[cat www/data/thought-registry | eval $({(if-param-then-else
"filter-tag"
#[-[grep '{filter-tag}']-]
#[-["grep -E '.*'"]-])}) | sort -t$'\t' -k4 -nr | while IFS=$'\t' read -r tags file_name title date description{";"} do echo "{thought-list-entry-format}"{";"} done]f]
#[-["grep -E '.*'"]-])}) | sort -t $'\t' -k4{{,.6,.9}}rn | while IFS=$'\t' read -r tags file_name title date description{";"} do echo "{thought-list-entry-format}"{";"} done]f]
:echo False)}]]f]))))

2
www/src/scripts/hl-command Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
eval "${1}" | aha -s | pup 'pre' --pre

View File

@ -1,7 +1,7 @@
#!/bin/bash
tmpfile=$(mktemp)
timeout 0.6s nvim "$1" -c "set nonu | set nornu" -c "autocmd LspAttach * execute 'TOhtml | w! ${tmpfile} | qa!'" >/dev/null || nvim "$1" -c "set nonu | set nornu" -c "TOhtml | w! ${tmpfile} | qa!" >/dev/null
timeout 0.6s nvim "$1" -c "set noswapfile" -c "%s/^$/ /g" -c "set conceallevel=0" -c "set nonu | set nornu" -c "autocmd LspAttach * execute 'TOhtml | w! ${tmpfile} | qa!'" >/dev/null || nvim "$1" -c "set noswapfile" -c "%s/^$/ /" -c "set conceallevel=0" -c "set nonu | set nornu" -c "TOhtml | w! ${tmpfile} | qa!" >/dev/null
cat "${tmpfile}" | pup --pre 'style, pre' | sed '/^$/d'

View File

@ -1,8 +1,8 @@
(import templates.header [header])
(import templates.footer [footer])
(import utils [run hy-env])
(defn page [html
[title "natalieee.net"]
[author "natalie roentgen connolly"]
@ -18,7 +18,9 @@
body {display: none}]])
(link (:rel preload :href "/assets/style.css" :as style))
(link (:rel preload :href "/assets/bg.svg" :as image))
(link (:rel preload :href "/assets/ansi-colors.css" :as style))
(link (:rel "stylesheet" :href "/assets/style.css" :type "text/css"))
(link (:rel "stylesheet" :href "/assets/ansi-colors.css" :type "text/css"))
(meta (:http-equiv "content-type" :content "text/html; charset=utf-8"))
(meta (:name "viewport" :content "width=device-width, initial-scale=1"))
(meta (:name "author" :content ~author))