all repos

olexsmir.xyz @ 5994991

my site, yes, i like lua
42 files changed, 91 insertions(+), 1700 deletions(-)
feat: site
Author: olexsmir olexsmir@gmail.com
Committed at: 2025-11-30 21:15:38 +0000
Parent: 262f5ad
D

@@ -1,46 +0,0 @@

-name: ci - -on: - workflow_dispatch: - push: - branches: - - main - -jobs: - build: - runs-on: ubuntu-latest - name: Build and Push - steps: - - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version-file: go/go.mod - cache-dependency-path: go/go.mod - - - name: Install nvim - uses: rhysd/action-setup-vim@v1 - with: - neovim: true - version: nightly - - - name: build markdown parser - run: make build-parser - - - name: tests - run: | - nvim --version - make test - - - name: build - run: make build - - - name: publish - uses: s0/git-publish-subdir-action@develop - env: - REPO: self - BRANCH: gh-pages - FOLDER: build - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MESSAGE: "{msg}"
D

@@ -1,5 +0,0 @@

-/.tests/ -/.lux/ -/build/ -*.so -*.h
A 404.html

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

+<!DOCTYPE html><html lang="en"><head><title>Not found</title><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"><link href="https://olexsmir.xyz/feed.xml" rel="alternate" title="olexsmir's blog feed, also hi rss reader!" type="application/atom+xml"></link><link href="style.css" rel="stylesheet"></link><link href="assets/favicon.svg" rel="icon"></link><meta content="Page you're looking for, not found" name="description"><meta content="Page you're looking for, not found" property="og:description"><meta content="Not found" property="og:site_name"><meta content="Not found" property="og:title"><meta content="website" property="og:type"></head><body class="home"><header><nav><p><a class="visual-hidden" href="#main">Skip to content</a><a href="/">home</a><a href="/posts">posts</a><a href="/feed.xml">feed</a><button id="theme-toggle">🌓</button></p></nav></header><main id="main"><h1>There's nothing here!</h1><p>Go pack to the <a href="/">home page</a></p></main><script>const root = document.documentElement;root.dataset.theme = localStorage.theme || 'dark';document.getElementById('theme-toggle').onclick = () => {root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';localStorage.theme = root.dataset.theme;};</script></body></html>
M CNAME

@@ -1,1 +1,1 @@

-olexsmir.xyz+olexsmir.xyz
D

@@ -1,26 +0,0 @@

- GLWTS(Good Luck With That Shit) Public License - Copyright (c) Every-fucking-one, except the Author - -Everyone is permitted to copy, distribute, modify, merge, sell, publish, -sublicense or whatever the fuck they want with this software but at their -OWN RISK. - - Preamble - -The author has absolutely no fucking clue what the code in this project -does. It might just fucking work or not, there is no third option. - - - GOOD LUCK WITH THAT SHIT PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION - - 0. You just DO WHATEVER THE FUCK YOU WANT TO as long as you NEVER LEAVE -A FUCKING TRACE TO TRACK THE AUTHOR of the original product to blame for -or hold responsible. - -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -Good luck and Godspeed.
A chroma.css

@@ -0,0 +1,70 @@

+.bg { color: #c0caf5; background-color: #1a1b26; } +.chroma { color: #c0caf5; background-color: #1a1b26; } +.chroma .err { color: #db4b4b } +.chroma .lnlinks { outline: none; text-decoration: none; color: inherit } +.chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } +.chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } +.chroma .hl { background-color: #414868 } +.chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #a9b1d6 } +.chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #a9b1d6 } +.chroma .line { display: flex; } +.chroma .k { color: #bb9af7 } +.chroma .kc { color: #e0af68 } +.chroma .kd { color: #9d7cd8 } +.chroma .kn { color: #7dcfff } +.chroma .kp { color: #bb9af7 } +.chroma .kr { color: #bb9af7 } +.chroma .kt { color: #41a6b5 } +.chroma .na { color: #7aa2f7 } +.chroma .nc { color: #ff9e64 } +.chroma .no { color: #ff9e64 } +.chroma .nd { color: #7aa2f7; font-weight: bold } +.chroma .ni { color: #7dcfff } +.chroma .ne { color: #e0af68 } +.chroma .nl { color: #9ece6a } +.chroma .nn { color: #e0af68 } +.chroma .py { color: #e0af68 } +.chroma .nt { color: #bb9af7 } +.chroma .nb { color: #9ece6a } +.chroma .bp { color: #9ece6a } +.chroma .nf { color: #7aa2f7 } +.chroma .fm { color: #7aa2f7 } +.chroma .s { color: #9ece6a } +.chroma .sa { color: #9d7cd8 } +.chroma .sb { color: #9ece6a } +.chroma .sc { color: #9ece6a } +.chroma .dl { color: #7aa2f7 } +.chroma .sd { color: #414868 } +.chroma .s2 { color: #9ece6a } +.chroma .se { color: #7aa2f7 } +.chroma .sh { color: #414868 } +.chroma .si { color: #9ece6a } +.chroma .sx { color: #9ece6a } +.chroma .sr { color: #7dcfff } +.chroma .s1 { color: #9ece6a } +.chroma .ss { color: #9ece6a } +.chroma .m { color: #e0af68 } +.chroma .mb { color: #e0af68 } +.chroma .mf { color: #e0af68 } +.chroma .mh { color: #e0af68 } +.chroma .mi { color: #e0af68 } +.chroma .il { color: #e0af68 } +.chroma .mo { color: #e0af68 } +.chroma .o { color: #9ece6a; font-weight: bold } +.chroma .ow { color: #9ece6a; font-weight: bold } +.chroma .c { color: #414868; font-style: italic } +.chroma .ch { color: #414868; font-style: italic } +.chroma .cm { color: #414868; font-style: italic } +.chroma .c1 { color: #414868; font-style: italic } +.chroma .cs { color: #414868; font-style: italic } +.chroma .cp { color: #414868; font-style: italic } +.chroma .cpf { color: #414868; font-weight: bold; font-style: italic } +.chroma .gd { color: #db4b4b; background-color: #15161e } +.chroma .ge { font-style: italic } +.chroma .gr { color: #db4b4b } +.chroma .gh { color: #e0af68; font-weight: bold } +.chroma .gi { color: #9ece6a; background-color: #15161e } +.chroma .gs { font-weight: bold } +.chroma .gu { color: #e0af68; font-weight: bold } +.chroma .gt { color: #db4b4b } +.chroma .gl { text-decoration: underline }
A feed.xml

@@ -0,0 +1,7 @@

+<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>olexsmir's blog</title><subtitle>olexsmir's blog feed, also hi rss reader!</subtitle><id>https://olexsmir.xyz/</id><link href="https://olexsmir.xyz" rel="alternate"></link><link href="https://olexsmir.xyz/feed.xml" rel="self" type="application/atom+xml"></link><updated>2025-11-20T00:00:00+02:00</updated><author><name>olexsmir</name><email>olexsmir@gmail.com</email></author><entry><title>What am I up to nowadays?</title><link href="https://olexsmir.xyz/now"></link><id>https://olexsmir.xyz/now</id><updated>2025-11-20T00:00:00+02:00</updated><content type="html">&lt;ul&gt; +&lt;li&gt;Probably, tweaking this site, and it&#39;s &lt;a href=&quot;https://github.com/olexsmir/olexsmir.xyz&quot;&gt;SSG&lt;/a&gt;.&lt;/li&gt; +&lt;li&gt;Forcing my self to go outside and sleep well.&lt;/li&gt; +&lt;li&gt;Enjoying programming.&lt;/li&gt; +&lt;li&gt;Hating russia and wanting more of електрохарчування.&lt;/li&gt; +&lt;/ul&gt; +</content></entry></feed>
D

@@ -1,13 +0,0 @@

-module olexsmir.xyz - -go 1.25.3 - -require ( - github.com/alecthomas/chroma/v2 v2.20.0 // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/wyatt915/goldmark-treeblood v0.0.1 // indirect - github.com/wyatt915/treeblood v0.1.16 // indirect - github.com/yuin/goldmark v1.7.13 // indirect - github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect - gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab // indirect -)
D

@@ -1,28 +0,0 @@

-github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY= -github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= -github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= -github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= -github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= -github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/wyatt915/goldmark-treeblood v0.0.1 h1:6vLJcjFrHgE4ASu2ga4hqIQmbvQLU37v53jlHZ3pqDs= -github.com/wyatt915/goldmark-treeblood v0.0.1/go.mod h1:SmcJp5EBaV17rroNlgNQFydYwy0+fv85CUr/ZaCz208= -github.com/wyatt915/treeblood v0.1.16 h1:byxNbWZhnPDxdTp7W5kQhCeaY8RBVmojTFz1tEHgg8Y= -github.com/wyatt915/treeblood v0.1.16/go.mod h1:i7+yhhmzdDP17/97pIsOSffw74EK/xk+qJ0029cSXUY= -github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= -github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= -github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= -gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab h1:gK9tS6QJw5F0SIhYJnGG2P83kuabOdmWBbSmZhJkz2A= -gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab/go.mod h1:SPu13/NPe1kMrbGoJldQwqtpNhXsmIuHCfm/aaGjU0c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
D

@@ -1,76 +0,0 @@

-package main - -// #include <stdlib.h> -import "C" - -import ( - "bytes" - "unsafe" - - "github.com/alecthomas/chroma/v2/formatters" - chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/styles" - treeblood "github.com/wyatt915/goldmark-treeblood" - "github.com/yuin/goldmark" - highlighting "github.com/yuin/goldmark-highlighting/v2" - "github.com/yuin/goldmark/extension" - "github.com/yuin/goldmark/renderer/html" - callout "gitlab.com/staticnoise/goldmark-callout" -) - -func main() {} - -//export md_to_html -func md_to_html(input *C.char) *C.char { - if input == nil { - return C.CString("") - } - - inp := C.GoString(input) - md := goldmark.New( - goldmark.WithExtensions( - extension.GFM, - highlighting.NewHighlighting( - highlighting.WithFormatOptions( - chromahtml.Standalone(false), - chromahtml.WithClasses(true), - ), - ), - extension.NewFootnote( - extension.WithFootnoteIDPrefix([]byte("footnote")), - ), - treeblood.MathML(), - callout.CalloutExtention, - ), - goldmark.WithRendererOptions(html.WithUnsafe()), - ) - - var buf bytes.Buffer - if err := md.Convert([]byte(inp), &buf); err != nil { - return C.CString("") - } - - return C.CString(buf.String()) -} - -//export chroma_css -func chroma_css(theme *C.char) *C.char { - if theme == nil { - return C.CString("") - } - thm := C.GoString(theme) - - var buf bytes.Buffer - formatter := formatters.Get("html").(*chromahtml.Formatter) - if err := formatter.WriteCSS(&buf, styles.Get(thm)); err != nil { - return C.CString("") - } - return C.CString(buf.String()) -} - -//export free_cstring -func free_cstring(s *C.char) { - if s != nil { - C.free(unsafe.Pointer(s)) - } -}
A index.html

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

+<!DOCTYPE html><html lang="en"><head><title>olexsmir.xyz</title><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"><link href="https://olexsmir.xyz/feed.xml" rel="alternate" title="olexsmir's blog feed, also hi rss reader!" type="application/atom+xml"></link><link href="style.css" rel="stylesheet"></link><link href="assets/favicon.svg" rel="icon"></link><meta content="olexsmir.xyz home page" name="description"><meta content="olexsmir.xyz home page" property="og:description"><meta content="olexsmir.xyz" property="og:site_name"><meta content="olexsmir.xyz" property="og:title"><meta content="website" property="og:type"></head><body class="home"><header><nav><p><a class="visual-hidden" href="#main">Skip to content</a><a href="/">home</a><a href="/posts">posts</a><a href="/feed.xml">feed</a><button id="theme-toggle">🌓</button></p></nav></header><main id="main"><h2>Hi, I'm Olex from Ukraine 🇺🇦</h2><p>Welcome to my corner of the internet. Here I share what I find interesting. Hopefully I will maintain the content on this site, not only it’s code.</p><p>Feel free to scroll through the posts below or subscribe to the <a href="/feed.xml">RSS feed</a> for updates. And if you want to say hi, mail me at <a href="mailto:olexsmir@gmail.com">olexsmir@gmail.com</a> or message me on <a href="https://t.me/olexsmir">telegram</a> if that's your cup of tea.</p><p>If you’re curious what I’m up to, check out <a href="/now">now</a> page, or look through <a href="https://github.com/olexsmir">github</a> or <a href="https://tangled.org/olexsmir.xyz">tangled</a> accounts.</p><div class="recent-posts"><ul class="blog-posts"></ul></div></main><script>const root = document.documentElement;root.dataset.theme = localStorage.theme || 'dark';document.getElementById('theme-toggle').onclick = () => {root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';localStorage.theme = root.dataset.theme;};</script></body></html>
A json2go.html

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

+<!DOCTYPE html><html><head><meta content="olexsmir.xyz/json2go git https://github.com/olexsmir/json2go" name="go-import"><meta content="olexsmir.xyz/json2go https://github.com/olexsmir/json2go https://github.com/olexsmir/json2go/tree/main{/dir} https://github.com/olexsmir/json2go/blob/main{/dir}/{file}#L{line}" name="go-source"></head><body><p>Source: <a href="https://github.com/olexsmir/json2go">https://github.com/olexsmir/json2go</a></p><code>$ go get olexsmir.xyz/json2go</code></body></html>
D

@@ -1,21 +0,0 @@

-local config = {} - -config.name = "olexsmir" -config.title = "olexsmir's blog" -config.email = "olexsmir@gmail.com" -config.cname = "olexsmir.xyz" -config.url = "https://" .. config.cname -config.feed = { - url = config.url .. "/feed.xml", - subtitle = "olexsmir's blog feed, also hi rss reader!", -} - -config.build = { - chroma_theme = "tokyonight-night", - - output = "build", - assets = "assets", - posts = "posts", -} - -return config
D

@@ -1,73 +0,0 @@

-local css = require "lego.css" -local file = require "lego.file" -local html = require "lego.html" -local liblego = require "liblego" -local post = require "lego.post" -local rss = require "lego.rss" -local sitemap = require "lego.sitemap" - -local c = require "blog.config" -local pages = require "blog.pages" -local styles = require "blog.styles" -local blog = {} - -local function write(fpath, data) - file.write({ c.build.output, fpath }, data) -end - -local function write_page(fpath, node) - write(fpath, html.render_page(node)) -end - -local function write_gopkg(name, repo, branch) - branch = branch or "main" - write(name .. ".html", html.render_page(pages.gopkg(name, repo, branch))) -end - -function blog.build() - --- clean up - if file.is_dir(c.build.output) then - file.rm(c.build.output) - end - file.mkdir(c.build.output) - file.copy_dir(c.build.assets, vim.fs.joinpath(c.build.output, c.build.assets)) - - -- write the pages - ---@type lego.Post[] - local posts = vim - .iter(file.list_dir(c.build.posts)) - :map(function(fname) - return post.read_file { c.build.posts, fname } - end) - :totable() - post.sort_by_date(posts) - - write("CNAME", c.cname) - write("chroma.css", liblego.get_css(c.build.chroma_theme)) - write("sitemap.xml", sitemap.sitemap(posts, { site_url = c.url })) - write("style.css", css.style(styles)) - write_page("404.html", pages.not_found()) - write_page("index.html", pages.home(posts)) - write_page("posts.html", pages.posts(posts)) - - write_gopkg("json2go", "https://github.com/olexsmir/json2go") - - -- stylua: ignore - write("feed.xml", rss.rss(posts, { - email = c.email, - name = c.name, - title = c.title, - subtitle = c.feed.subtitle; - feed_url = c.feed.url, - home_url = c.url, - })) - - for _, p in pairs(posts) do - local phtml = html.render_page(pages.post(p)) - write(p.meta.slug .. ".html", phtml) - end - - file.report_duplicates() -end - -return blog
D

@@ -1,186 +0,0 @@

-local a = require "lego.html.attribute" -local c = require "blog.config" -local h = require "lego.html" -local pages = {} - -local function themeSwitcherScript() - local s = [[ - const root = document.documentElement; - root.dataset.theme = localStorage.theme || 'dark'; - document.getElementById('theme-toggle').onclick = () => { - root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark'; - localStorage.theme = root.dataset.theme; - }; - ]] - s = s:gsub(" ", "") - s = vim.split(s, "\n") ---@diagnostic disable-line: cast-local-type - s = table.concat(s, "") - return h.el("script", {}, { h.text(s) }) -end - ----@param o {title:string, desc:string, has_code:boolean, body:lego.HtmlNode[]} ----@return lego.HtmlNode -local function with_page(o) - return h.el("html", { a.attr("lang", "en") }, { - h.el("head", {}, { - h.title({}, { h.text(o.title) }), - h.meta { a.attr("charset", "utf-8") }, - h.meta { - a.attr("name", "viewport"), - a.attr("content", "width=device-width, initial-scale=1.0, viewport-fit=cover"), - }, - h.link { - a.attr("rel", "alternate"), - a.attr("type", "application/atom+xml"), - a.attr("title", c.feed.subtitle), - a.href(c.feed.url), - }, - h.link { a.attr("rel", "stylesheet"), a.href "style.css" }, - o.has_code and h.link { a.attr("rel", "stylesheet"), a.href "chroma.css" } or {}, - h.link { a.attr("rel", "icon"), a.href "assets/favicon.svg" }, - h.meta { a.attr("name", "description"), a.attr("content", o.desc) }, - h.meta { a.attr("property", "og:description"), a.attr("content", o.desc) }, - h.meta { a.attr("property", "og:site_name"), a.attr("content", o.title) }, - h.meta { a.attr("property", "og:title"), a.attr("content", o.title) }, - h.meta { a.attr("property", "og:type"), a.attr("content", "website") }, - }), - h.el("body", { a.class "home" }, { - h.el("header", {}, { - h.nav({}, { - h.p({}, { - h.a({ a.class "visual-hidden", a.href "#main" }, { "Skip to content" }), - h.a({ a.href "/" }, { h.text "home" }), - h.a({ a.href "/posts" }, { h.text "posts" }), - h.a({ a.href "/feed.xml" }, { h.text "feed" }), - h.el("button", { a.id "theme-toggle" }, { h.text "🌓" }), - }), - }), - }), - h.main({ a.id "main" }, o.body), - themeSwitcherScript(), - }), - }) -end - ----@param iter Iter ----@return string|lego._HtmlNote -local function list_posts(iter) - return h.ul( - { a.class "blog-posts" }, - iter - ---@param post lego.Post - :filter(function(post) - return not post.hidden - end) - ---@param post lego.Post - :map(function(post) - return h.li({}, { - h.span({}, { - h.el("i", {}, { h.time(post.meta.date) }), - }), - h.a({ a.href(post.meta.slug) }, { h.text(post.meta.title) }), - }) - end) - :totable() - ) -end - ----@param posts lego.Post[] -function pages.home(posts) - return with_page { - title = "olexsmir.xyz", - desc = "olexsmir.xyz home page", - body = { - h.h2({}, { "Hi, I'm Olex from Ukraine 🇺🇦" }), - h.p({}, { - "Welcome to my corner of the internet. Here I share what I find interesting. ", - "Hopefully I will maintain the content on this site, not only it’s code.", - }), - h.p({}, { - "Feel free to scroll through the posts below or subscribe to the ", - h.a({ a.href "/feed.xml" }, { "RSS feed" }), - " for updates. ", - "And if you want to say hi, mail me at ", - h.a({ a.href("mailto:" .. c.email) }, { c.email }), - " or message me on ", - h.a({ a.href "https://t.me/olexsmir" }, { "telegram" }), - " if that's your cup of tea.", - }), - h.p({}, { - "If you’re curious what I’m up to, check out ", - h.a({ a.href "/now" }, { "now" }), - " page, or look through ", - h.a({ a.href "https://github.com/olexsmir" }, { "github" }), - " or ", - h.a({ a.href "https://tangled.org/olexsmir.xyz" }, { "tangled" }), - " accounts.", - }), - h.div({ a.class "recent-posts" }, { - list_posts(vim.iter(posts):take(7)), - }), - }, - } -end - -function pages.not_found() - return with_page { - title = "Not found", - desc = "Page you're looking for, not found", - body = { - h.h1({}, { h.text "There's nothing here!" }), - h.p({}, { - h.text "Go pack to the ", - h.a({ a.href "/" }, { h.text "home page" }), - }), - }, - } -end - ----@param posts lego.Post[] -function pages.posts(posts) - return with_page { - title = "All olexsmir's posts", - desc = "List of all blog posts on the lego.", - body = { list_posts(vim.iter(posts)) }, - } -end - ----@param post lego.Post -function pages.post(post) - return with_page { - title = post.meta.title, - desc = "Blog post titled: " .. post.meta.title, - has_code = post.content:match "code" ~= nil, - body = { - h.div({ a.class "blog-title" }, { - h.h1({}, { h.text(post.meta.title) }), - h.p({}, { h.time(post.meta.date) }), - }), - h.raw(post.content), - }, - } -end - -function pages.gopkg(name, repo, branch) - local gomod = c.cname .. "/" .. name - local go_import = gomod .. " git " .. repo - local go_source = table.concat({ - gomod, - repo, - repo .. "/tree/" .. branch .. "{/dir}", - repo .. "/blob/" .. branch .. "{/dir}/{file}#L{line}", - }, " ") - - return h.el("html", {}, { - h.el("head", {}, { - h.meta { a.attr("name", "go-import"), a.attr("content", go_import) }, - h.meta { a.attr("name", "go-source"), a.attr("content", go_source) }, - }), - h.el("body", {}, { - h.p({}, { "Source: ", h.a({ a.href(repo) }, { repo }) }), - h.el("code", {}, { "$ go get " .. gomod }), - }), - }) -end - -return pages
D

@@ -1,154 +0,0 @@

-local text_mutted = "color-mix(in srgb, var(--text-color) 70%, var(--background-color))" - -return { - [":root"] = { - ["--background-color"] = "#131418", - ["--heading-color"] = "#eaeaea", - ["--text-color"] = "#babdc4", - ["--text-mutted"] = text_mutted, - ["--link-color"] = "#82aee3", - ["--code-background-color"] = "#2d2d2d", - }, - - ["[data-theme='light']"] = { - ["--background-color"] = "#fff", - ["--heading-color"] = "#0d122b", - ["--text-color"] = "#434648", - ["--text-mutted"] = text_mutted, - ["--link-color"] = "#003fff", - ["--code-background-color"] = "#d8dbe2", - }, - - ["#theme-toggle"] = { - all = "unset", - padding = "4px 8px", - border_radius = "10px", - cursor = "pointer", - ["&:hover"] = { background = "rgba(0,0,0,0.09)" }, - }, - - -- HEADER - - header = { padding_bottom = "0.3rem" }, - - [".visual-hidden:not(:focus)"] = { - position = "absolute", - bottom = "100%", - }, - - ["nav a"] = { margin_right = "0.9rem" }, - ["nav p"] = { margin_bottom = "0px" }, - - -- GENERAL - - body = { - max_width = "66ch", - margin = "0 auto", - padding = "0.6rem 1.7rem 3.1rem", - font_family = 'system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif', - font_size = "1rem", - background_color = "var(--background-color)", - color = "var(--text-color)", - line_height = "1.5", - letter_spacing = "0.005em", - overflow_wrap = "anywhere", - }, - - a = { - color = "var(--link-color)", - cursor = "pointer", - text_decoration = "none", - - ["&:hover"] = { text_decoration = "underline" }, - }, - - main = { - padding_top = "1.3rem", - line_height = "1.6", - - a = { - color = "var(--text-color)", - text_decoration = "underline", - - ["&:hover"] = { color = "var(--link-color)" }, - }, - }, - - -- POSTS LIST - - [".recent-posts"] = { - padding_top = "0.5rem", - ["& span"] = {}, - }, - - ["ul.blog-posts"] = { - list_style_type = "none", - padding = "unset", - ["& li"] = { display = "flex" }, - ["& li span"] = { flex = "0 0 130px" }, - ["& a"] = { - text_decoration = "none", - color = "var(--link-color)", - - ["&:hover"] = { text_decoration = "underline" }, - ["&:visited"] = { color = "var(--link-color)" }, - }, - }, - - time = { - color = "var(--text-mutted)", - font_family = "monospace", - font_style = "normal", - font_size = "0.95rem", - }, - - -- POST - [".blog-title"] = { - p = { margin_top = "0px" }, - h1 = { - margin_top = "0px", - margin_bottom = "0px", - }, - }, - - ["h1, h2, h3, h4, h5, h6"] = { - font_family = "var(--font-main)", - color = "var(--heading-color)", - }, - - i = { font_style = "italic" }, - img = { - max_width = "100%", - display = "block", - justify_self = "center", - }, - - table = { width = "100%" }, - ["strong, b"] = { color = "var(--heading-color)" }, - - button = { - margin = "0", - cursor = "pointer", - }, - - hr = { - border = "0", - border_top = "1px dashed", - }, - - pre = { - padding = "10px", - border_radius = "6px", - code = { - padding = "0", - border_radius = "0", - }, - }, - - code = { - font_family = "monospace", - background_color = "var(--code-background-color)", - white_space = "pre-wrap", - padding = "0 0.3em", - }, -}
D

@@ -1,87 +0,0 @@

-local css = {} - ----@param str string ----@return string -local function to_kebab_case(str) - str = str:gsub("_", "-") - str = str:gsub("([a-z])([A-Z])", "%1-%2"):lower() -- Convert camelCase to kebab-case - return str -end - ----@param value string|number ----@return string -local function value_to_css(value) - if type(value) == "number" then - return tostring(value) - end - return string.format("%s", value) -end - -local function render_properties(properties) - local parts = {} - local keys = {} - for key in pairs(properties) do - table.insert(keys, key) - end - table.sort(keys) - - for _, key in ipairs(keys) do - local value = properties[key] - table.insert(parts, string.format("%s:%s", to_kebab_case(key), value_to_css(value))) - end - - return table.concat(parts, ";") -end - -local function flatten_css_rules(rules) - local all_rules = {} - - local function process_rule(sel, props, prefix) - local full_sel - if sel:find "^&" then - full_sel = prefix .. sel:gsub("^&", "") - else - full_sel = prefix and (prefix .. " " .. sel) or sel - end - local flat_props = {} - for k, v in pairs(props) do - if type(v) == "table" then - process_rule(k, v, full_sel) - else - flat_props[k] = v - end - end - if next(flat_props) then - all_rules[full_sel] = flat_props - end - end - - for sel, props in pairs(rules) do - process_rule(sel, props, nil) - end - - return all_rules -end - ----@param rules table ----@return string -function css.style(rules) - local all_rules = flatten_css_rules(rules) - - local selectors = {} - for s in pairs(all_rules) do - table.insert(selectors, s) - end - table.sort(selectors) - - local rule_parts = {} - for _, sel in ipairs(selectors) do - local props = all_rules[sel] - local props_str = render_properties(props) .. ";" - table.insert(rule_parts, string.format("%s{%s}", sel, props_str)) - end - - return table.concat(rule_parts, "") -end - -return css
D

@@ -1,9 +0,0 @@

-local date = {} - ----@param d string ----@return string -function date.date(d) - return d .. "T00:00:00+02:00" -end - -return date
D

@@ -1,86 +0,0 @@

-local file = {} -local _writes = {} - -function file.report_duplicates() - local freq = {} - local duplicates = {} - - for _, v in ipairs(_writes) do - freq[v] = (freq[v] or 0) + 1 - end - - for value, count in pairs(freq) do - if count > 1 then - duplicates[#duplicates + 1] = value - end - end - - if #duplicates > 0 then - vim.print("duplicates " .. vim.inspect(duplicates)) - end -end - ----@alias lego.FilePath string|string[] - ----@param p lego.FilePath ----@return string -function file.to_path(p) - if type(p) == "table" then - return vim.fs.joinpath(unpack(p)) - end - return p -end - ----@param path lego.FilePath ----@return string[] -function file.list_dir(path) - return vim.fn.readdir(file.to_path(path)) -end - ----@param path lego.FilePath -function file.read(path) - return vim.fn.readfile(file.to_path(path)) -end - ----@param path lego.FilePath ----@param content string -function file.write(path, content) - path = file.to_path(path) - table.insert(_writes, path) - - vim.print("writing " .. path) - vim.fn.writefile(vim.split(content, "\n", { plain = true }), path) -end - ----@param path lego.FilePath -function file.rm(path) - path = file.to_path(path) - vim.print("deleting " .. path) - vim.fs.rm(path, { force = true, recursive = true }) -end - ----@param path string -function file.mkdir(path) - vim.fn.mkdir(file.to_path(path), "p") -end - ----@param path string -function file.is_dir(path) - local build_dir_stats = vim.uv.fs_stat(file.to_path(path)) - return not build_dir_stats or build_dir_stats.type == "directory" -end - ----@param from string ----@param to string -function file.copy_dir(from, to) - from = file.to_path(from) - to = file.to_path(to) - vim.print("copying " .. to) - - file.mkdir(to) - for _, f in ipairs(vim.fn.readdir(from)) do - vim.uv.fs_copyfile(vim.fs.joinpath(from, f), vim.fs.joinpath(to, f)) - end -end - -return file
D

@@ -1,46 +0,0 @@

-local frontmatter = {} - ----@param lines string[] ----@return table -function frontmatter.extract(lines) - if lines[1] ~= "---" then - return {} - end - - for i = 2, #lines do - if lines[i] == "---" then - local frontmatter_lines = { unpack(lines, 2, i - 1) } - - local result = {} - for _, line in ipairs(frontmatter_lines) do - local key, value = line:match "^%s*(.-)%s*=%s*(.-)%s*$" - if key and value then - result[key] = value - end - end - - return result - end - end - - return {} -end - ----@param lines string[] ----@return string[]|nil -function frontmatter.content(lines) - if lines[1] ~= "---" then - return lines - end - - return vim - .iter(lines) - :skip(1) - :skip(function(el) - return el ~= "---" - end) - :skip(1) - :totable() -end - -return frontmatter
D

@@ -1,27 +0,0 @@

-local hattribute = {} - ----@class lego.HtmlAttribute ----@field [string] string - ----@param attribute string ----@param value string ----@return lego.HtmlAttribute -function hattribute.attr(attribute, value) - return { [attribute] = value } -end - --- COMMON ATTRIBUTES --- stylua: ignore start - ----@param class string -function hattribute.class(class) return hattribute.attr("class", class) end - ----@param link string -function hattribute.href(link) return hattribute.attr("href", link) end - ----@param id string -function hattribute.id(id) return hattribute.attr("id", id) end - --- stylua: ignore end - -return hattribute
D

@@ -1,175 +0,0 @@

-local a = require "lego.html.attribute" -local html = {} - ----@alias lego.HtmlNode lego._HtmlNote|string - ----@class lego._HtmlNote ----@field tag string ----@field attributes lego.HtmlAttribute[] ----@field children lego._HtmlNote[] - ----@param tag string ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] ----@return lego.HtmlNode -function html.el(tag, attributes, children) - local attrs = {} - for _, attr_table in ipairs(attributes or {}) do - for k, v in pairs(attr_table) do - attrs[k] = v - end - end - - return { - tag = tag, - attributes = attrs, - children = children or {}, - } -end - ----@param text string ----@return lego.HtmlNode -function html.text(text) - return text -end - ----@param html_str string ----@return lego.HtmlNode -function html.raw(html_str) - return html_str -end - ----@param txts string[] ----@return string[] -function html.tt(txts) - local tt = vim - .iter(txts) - :map(function(txt) - return { txt, " " } - end) - :flatten() - :totable() - - if tt[#tt] == " " then - table.remove(tt, #tt) - end - - return tt -end - -local _self_closing_tags = { - area = {}, - base = {}, - br = {}, - col = {}, - embed = {}, - hr = {}, - img = {}, - input = {}, - -- link = {}, -- ignored because it needed for rss - meta = {}, - source = {}, - track = {}, - wbr = {}, -} - ----@param node lego.HtmlNode ----@return string -function html.render(node) - if type(node) == "string" then - return node - elseif type(node) == "table" and node.tag then - local attr_keys = {} - for k in pairs(node.attributes or {}) do - table.insert(attr_keys, k) - end - table.sort(attr_keys) - - local attrs_str = "" - for _, v in pairs(attr_keys) do - attrs_str = attrs_str .. string.format(' %s="%s"', v, node.attributes[v]) - end - - if _self_closing_tags[node.tag] then - return string.format("<%s%s>", node.tag, attrs_str) - end - - local children_str = "" - for _, child in ipairs(node.children or {}) do - children_str = children_str .. html.render(child) - end - - return string.format("<%s%s>%s</%s>", node.tag, attrs_str, children_str, node.tag) - end - return "" -end - -function html.render_page(node) - return "<!DOCTYPE html>" .. html.render(node) -end - --- --- COMMON ELEMENTS --- stylua: ignore start - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.div(attributes, children) return html.el("div", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.main(attributes, children) return html.el("main", attributes, children) end - ----@param attributes lego.HtmlAttribute[] -function html.meta(attributes) return html.el("meta", attributes, {}) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.span(attributes, children) return html.el("span", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.p(attributes, children) return html.el("p", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.a(attributes, children) return html.el("a", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.ul(attributes, children) return html.el("ul", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.li(attributes, children) return html.el("li", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.title(attributes, children) return html.el("title", attributes, children) end - ----@param attributes lego.HtmlAttribute[] -function html.link(attributes) return html.el("link", attributes, {}) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.h1(attributes, children) return html.el("h1", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.h2(attributes, children) return html.el("h2", attributes, children) end - ----@param attributes lego.HtmlAttribute[] ----@param children lego.HtmlNode[] -function html.nav(attributes, children) return html.el("nav", attributes, children) end - -function html.br() return html.el("br", {}, {}) end - ----@param datetime string -function html.time(datetime) - return html.el("time", - { a.attr("datetime", datetime) }, - { html.text(datetime)}) -end - --- stylua: ignore end - -return html
D

@@ -1,43 +0,0 @@

-local file = require "lego.file" -local frontmatter = require "lego.frontmatter" -local liblego = require "liblego" -local post = {} - ----@class lego.Post ----@field content string ----@field hidden boolean ----@field meta lego.PostMeta - ----@class lego.PostMeta ----@field title string ----@field date string ----@field slug string ----@field desc string - ----@param fpath lego.FilePath ----@return lego.Post -function post.read_file(fpath) - local p = file.read(fpath) - local content = table.concat(frontmatter.content(p) or {}, "\n") - local meta = frontmatter.extract(p) - local hidden = meta["hidden"] == "true" - assert(meta["title"] ~= nil, (file.to_path(fpath) .. " doesn't have title")) - assert(meta["date"] ~= nil, (file.to_path(fpath) .. " doesn't have date")) - assert(meta["slug"] ~= nil, (file.to_path(fpath) .. " doesn't have slug")) - - return { - meta = meta, - hidden = hidden, - content = liblego.md_to_html(content), - } -end - ----MUTATES THE TABLE ----@param posts lego.Post[] -function post.sort_by_date(posts) - table.sort(posts, function(a, b) - return a.meta.date > b.meta.date - end) -end - -return post
D

@@ -1,59 +0,0 @@

-local a = require "lego.html.attribute" -local formatDate = require("lego.date").date -local h = require "lego.html" -local rss = {} - -function rss.escape_html(html) - local map = { - ["&"] = "&amp;", - ["<"] = "&lt;", - [">"] = "&gt;", - ['"'] = "&quot;", - ["'"] = "&#39;", - } - - html = html:gsub("[%z\1-\8\11-\12\14-\31]", "") -- remove control chars - return (html:gsub("[&<>\"']", function(c) - return map[c] - end)) -end - ----@param config {feed_url:string, home_url:string, title:string, name:string, email:string, subtitle:string} ----@param posts lego.Post[] ----@return string -function rss.rss(posts, config) - local entries = vim - .iter(posts) - ---@param post lego.Post - :map(function(post) - return h.el("entry", {}, { - h.title({}, { h.text(post.meta.title) }), - h.link { a.href(config.home_url .. "/" .. post.meta.slug) }, - h.el("id", {}, { h.text(config.home_url .. "/" .. post.meta.slug) }), - h.el("updated", {}, { h.text(formatDate(post.meta.date)) }), - h.el("content", { a.attr("type", "html") }, { h.raw(rss.escape_html(post.content)) }), - }) - end) - :totable() - - return [[<?xml version="1.0" encoding="utf-8"?>]] - .. h.render(h.el("feed", { a.attr("xmlns", "http://www.w3.org/2005/Atom") }, { - h.title({}, { h.text(config.title) }), - h.el("subtitle", {}, { h.text(config.subtitle) }), - h.el("id", {}, { h.text(config.home_url .. "/") }), - h.link { a.href(config.home_url), a.attr("rel", "alternate") }, - h.link { - a.href(config.feed_url), - a.attr("rel", "self"), - a.attr("type", "application/atom+xml"), - }, - h.el("updated", {}, { h.text(formatDate(posts[1].meta.date)) }), - h.el("author", {}, { - h.el("name", {}, { h.text(config.name) }), - h.el("email", {}, { h.text(config.email) }), - }), - unpack(entries), - })) -end - -return rss
D

@@ -1,44 +0,0 @@

-local a = require "lego.html.attribute" -local formatDate = require("lego.date").date -local h = require "lego.html" -local sitemap = {} - ----@param opts {url:string, date:string, priority: string} -local function url(opts) - return h.el("url", {}, { - h.el("loc", {}, { h.text(opts.url) }), - h.el("lastmod", {}, { h.text(formatDate(opts.date)) }), - h.el("priority", {}, { h.text(opts.priority) }), - }) -end - ----@param posts lego.Post[] ----@param config {site_url:string} ----@return string -function sitemap.sitemap(posts, config) - local urls = vim - .iter(posts) - ---@param post lego.Post - :map(function(post) - return url { - url = config.site_url .. "/" .. post.meta.slug, - date = post.meta.date, - priority = "0.80", - } - end) - :totable() - - return h.render(h.el("urlset", { - a.attr("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"), - a.attr("xmlns:xhtml", "http://www.w3.org/1999/xhtml"), - }, { - url { - url = config.site_url, - date = posts[1].meta.date, - priority = "1.0", - }, - unpack(urls), - })) -end - -return sitemap
D

@@ -1,39 +0,0 @@

-local ffi = require "ffi" - -ffi.cdef [[ - void free_cstring(char* s); - char* md_to_html(const char* input); - char* chroma_css(const char* theme); -]] - -local lib = ffi.load "./go/liblego.so" - -local M = {} - ----@param markdown string ----@return string -function M.md_to_html(markdown) - local result = lib.md_to_html(markdown) - local html = ffi.string(result) - lib.free_cstring(result) - if html == "" then - error "failed, good luck" - end - return html -end - ----@param theme string ----@return string -function M.get_css(theme) - local result = lib.chroma_css(theme) - local css = ffi.string(result) - if css == "" then - error "failed, good luck" - end - - css = css:gsub("/%*.-%*/ ", "") - css = css:gsub("\n$", "") - return css -end - -return M
D

@@ -1,49 +0,0 @@

-local function if_test(fn) - if vim.env.TEST == "true" then - fn() - end -end - -local function root(p) - local f = debug.getinfo(1, "S").source:sub(2) - return vim.fn.fnamemodify(f, ":p:h:h") .. (p or "") -end - -local function install_plug(plugin) - local name = plugin:match ".*/(.*)" - local package_root = root ".tests/site/pack/deps/start/" - if not vim.uv.fs_stat(package_root .. name) then - print("Installing " .. plugin) - vim - .system({ - "git", - "clone", - "--depth=1", - "https://github.com/" .. plugin .. ".git", - package_root .. "/" .. name, - }) - :wait() - end -end - -if_test(function() - install_plug "echasnovski/mini.test" -end) - -vim.env.XDG_CONFIG_HOME = root ".tests/config" -vim.env.XDG_DATA_HOME = root ".tests/data" -vim.env.XDG_STATE_HOME = root ".tests/state" -vim.env.XDG_CACHE_HOME = root ".tests/cache" - -vim.opt.runtimepath:append(root()) -vim.opt.packpath:append(root ".tests/site") - -if_test(function() - require("mini.test").setup { - collect = { - find_files = function() - return vim.fn.globpath("lua/tests", "**/*_test.lua", true, true) - end, - }, - } -end)
D

@@ -1,92 +0,0 @@

-local t = require "tests.testutils" -local _, T, css = t.setup "css" - -local c = require "lego.css" - -css["simple css"] = function() - local rules = { - body = { - margin = 0, - ["font-family"] = "sans-serif", - }, - } - - t.eq(c.style(rules), [[body{font-family:sans-serif;margin:0;}]]) -end - -css["nested-styles"] = function() - local rules = { - body = { - margin = 0, - h1 = { - color = "red", - }, - }, - } - - t.eq(c.style(rules), [[body{margin:0;}body h1{color:red;}]]) -end - -css["camelCase properties"] = function() - local rules = { - [".button"] = { - backgroundColor = "blue", - fontSize = "14px", - }, - } - - t.eq(c.style(rules), [[.button{background-color:blue;font-size:14px;}]]) -end - -css["multiple selectors"] = function() - local rules = { - body = { margin = 0 }, - h1 = { color = "black" }, - } - - t.eq(c.style(rules), [[body{margin:0;}h1{color:black;}]]) -end - -css["deep nesting"] = function() - local rules = { - [".container"] = { - padding = "10px", - [".inner"] = { - margin = 0, - span = { fontWeight = "bold" }, - ["&:hover"] = { color = "blue" }, - }, - }, - } - - t.eq( - c.style(rules), - [[.container{padding:10px;}.container .inner{margin:0;}.container .inner span{font-weight:bold;}.container .inner:hover{color:blue;}]] - ) -end - -css["@media"] = function() - local rules = { - ["@media screen and (min-width: 1200px)"] = { - margin = 0, - }, - } - t.eq(c.style(rules), [[@media screen and (min-width: 1200px){margin:0;}]]) -end - -css[":root"] = function() - local rules = { - [":root"] = { - ["--h1-size"] = "3rem", - }, - - ["@media (true)"] = { - ["--h1-size"] = "2rem", - }, - - h1 = { font_size = "0px" }, - } - t.eq(c.style(rules), [[:root{--h1-size:3rem;}@media (true){--h1-size:2rem;}h1{font-size:0px;}]]) -end - -return T
D

@@ -1,11 +0,0 @@

-local t = require "tests.testutils" -local _, T, file = t.setup "filee" - -local f = require "lego.file" - -file["to_path"] = function() - t.eq(f.to_path "spec/fixture.md", "spec/fixture.md") - t.eq(f.to_path { "spec", "fixture.md" }, "spec/fixture.md") -end - -return T
D

@@ -1,9 +0,0 @@

---- -title = This is fixture -slug = testing -date = 2025-09-30 -desc = testing testers test ---- - -# Content -Here's the content.
D

@@ -1,88 +0,0 @@

-local t = require "tests.testutils" -local _, T, frontmatter = t.setup "frontmatter" - -local f = require "lego.frontmatter" - -frontmatter["should extract from frontmatter"] = function() - local input = { - "---", - "title=The title", - "link=test", - "---", - "the content is here", - } - - t.eq(f.extract(input), { - title = "The title", - link = "test", - }) -end - -frontmatter["support options with spaces"] = function() - local input = { - "---", - "title = The title", - "link one = some long thing here", - "---", - "the content is here", - } - - t.eq(f.extract(input), { - title = "The title", - ["link one"] = "some long thing here", - }) -end - -frontmatter["should return {} if there's no frontmatter"] = function() - local input = { - "there's no frontmatter", - "just text", - } - - t.eq(f.extract(input), {}) -end - -frontmatter["should return empty list if frontmatter is empty"] = function() - local input = { - "---", - "---", - "there's no frontmatter", - "just text", - } - - t.eq(f.extract(input), {}) -end - -frontmatter["should extract content"] = function() - local input = { - "---", - "title = The title", - "link one = some long thing here", - "---", - "the content is here", - "", - "something", - } - - t.eq(f.content(input), { - "the content is here", - "", - "something", - }) -end - -frontmatter["should extract content with no frontmatter"] = function() - local input = { - "the content is here", - "", - "something", - } - - t.eq(f.content(input), { - "the content is here", - "", - "something", - }) -end - -return T
D

@@ -1,82 +0,0 @@

-local t = require "tests.testutils" -local _, T, html = t.setup "html" - -local a = require "lego.html.attribute" -local h = require "lego.html" - -html["simple html"] = function() - local node = h.el("div", {}, { h.text "hello" }) - - t.eq(h.render(node), "<div>hello</div>") -end - -html["simple html with attrs"] = function() - local node = h.div({ a.attr("class", "some classes") }, { h.text "string" }) - t.eq(h.render(node), [[<div class="some classes">string</div>]]) -end - -html["self-closing tag"] = function() - local node = h.el("img", { a.attr("src", "image.png"), a.attr("alt", "Alt text") }, {}) - t.eq(h.render(node), [[<img alt="Alt text" src="image.png">]]) -end - -html["nested html"] = function() - local node = h.div({ a.class "container" }, { - h.el("h1", {}, { h.text "Title" }), - h.p({}, { h.text "Paragraph" }), - }) - - t.eq(h.render(node), [[<div class="container"><h1>Title</h1><p>Paragraph</p></div>]]) -end - -html["even more nested html"] = function() - local node = h.div({ a.class "container" }, { - h.el("h1", {}, { h.text "Title" }), - h.div({}, { - h.p({}, { - h.text "text", - h.a({ a.href "google.com" }, { h.text "google" }), - }), - }), - }) - - t.eq( - h.render(node), - [[<div class="container"><h1>Title</h1><div><p>text<a href="google.com">google</a></p></div></div>]] - ) -end - -html["simple page"] = function() - local node = h.el("html", { a.attr("lang", "en") }, { - h.el("head", {}, { - h.el("title", {}, { h.text "My Page" }), - }), - h.el("body", {}, { - h.el("h1", {}, { h.text "Welcome" }), - h.p({}, { h.text "This is a basic HTML page." }), - }), - }) - - t.eq( - h.render_page(node), - [[<!DOCTYPE html><html lang="en"><head><title>My Page</title></head><body><h1>Welcome</h1><p>This is a basic HTML page.</p></body></html>]] - ) -end - -html["row html can be 'embedded'"] = function() - local node = h.el("html", {}, { - h.el("head", {}, { h.el("title", {}, { - h.text "My Page", - }) }), - h.el("body", {}, { - h.raw "<row-element some-kind-of-tag>", - }), - }) - - t.eq( - h.render(node), - "<html><head><title>My Page</title></head><body><row-element some-kind-of-tag></body></html>" - ) -end - -return T
D

@@ -1,45 +0,0 @@

-local t = require "tests.testutils" -local _, T, post = t.setup "post" - -local p = require "lego.post" - -post["read fixture"] = function() - local inp = p.read_file { "lua", "tests", "fixture.md" } - - t.eq(inp.meta.date, "2025-09-30") - t.eq(inp.meta.slug, "testing") - t.eq(inp.meta.title, "This is fixture") - t.eq(inp.meta.desc, "testing testers test") - - t.eq(inp.content, "<h1>Content</h1>\n<p>Here's the content.</p>\n") -end - -post["sort_by_date"] = function() - local input = { - { meta = { date = "2025-09-30" } }, - { meta = { date = "2024-09-30" } }, - { meta = { date = "2025-08-30" } }, - { meta = { date = "2025-09-28" } }, - { meta = { date = "2025-06-30" } }, - { meta = { date = "2025-07-04" } }, - { meta = { date = "2025-06-21" } }, - { meta = { date = "2025-06-13" } }, - { meta = { date = "2025-06-21" } }, - } - - p.sort_by_date(input) - - t.eq(input, { - { meta = { date = "2025-09-30" } }, - { meta = { date = "2025-09-28" } }, - { meta = { date = "2025-08-30" } }, - { meta = { date = "2025-07-04" } }, - { meta = { date = "2025-06-30" } }, - { meta = { date = "2025-06-21" } }, - { meta = { date = "2025-06-21" } }, - { meta = { date = "2025-06-13" } }, - { meta = { date = "2024-09-30" } }, - }) -end - -return T
D

@@ -1,12 +0,0 @@

-local t = require "tests.testutils" -local _, T, rss = t.setup "rss" - -local r = require "lego.rss" - -rss["should escape html"] = function() - local input = "<p>Hello <a>world</a></p>" - - t.eq(r.escape_html(input), "&lt;p&gt;Hello &lt;a&gt;world&lt;/a&gt;&lt;/p&gt;") -end - -return T
D

@@ -1,37 +0,0 @@

----@class testutils -local testutils = {} - -local minit_path = vim.fn.expand "%:p:h" .. "minit.lua" - ----@param mod string Module name for which to create a nested test set. ----@return MiniTest.child child ----@return table T ----@return table mod_name -function testutils.setup(mod) - local child = MiniTest.new_child_neovim() - local T = MiniTest.new_set { - hooks = { - post_once = child.stop, - pre_case = function() - child.restart { "-u", minit_path } - end, - }, - } - - T[mod] = MiniTest.new_set {} - return child, T, T[mod] -end - ----@generic T ----@param a T ----@param b T -function testutils.eq(a, b) - return MiniTest.expect.equality(a, b) -end - ----@param msg? string -function testutils.skip(msg) - MiniTest.skip(msg) -end - -return testutils
D

@@ -1,16 +0,0 @@

-.PHONY: all build build-parser test - -CMD=nvim --clean -u ./lua/minit.lua - -test: - @TEST=true $(CMD) --headless -c "lua MiniTest.run()" - -build-parser: - @cd go; go build -buildmode=c-shared -o liblego.so - -build: - @$(CMD) --headless +"lua require'blog'.build()" +q - -dev: - @watchexec --watch posts --watch lua --exts lua,md -- "make build" & - @bunx http-server ./build -p 8080
A now.html

@@ -0,0 +1,7 @@

+<!DOCTYPE html><html lang="en"><head><title>What am I up to nowadays?</title><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"><link href="https://olexsmir.xyz/feed.xml" rel="alternate" title="olexsmir's blog feed, also hi rss reader!" type="application/atom+xml"></link><link href="style.css" rel="stylesheet"></link><link href="assets/favicon.svg" rel="icon"></link><meta content="Blog post titled: What am I up to nowadays?" name="description"><meta content="Blog post titled: What am I up to nowadays?" property="og:description"><meta content="What am I up to nowadays?" property="og:site_name"><meta content="What am I up to nowadays?" property="og:title"><meta content="website" property="og:type"></head><body class="home"><header><nav><p><a class="visual-hidden" href="#main">Skip to content</a><a href="/">home</a><a href="/posts">posts</a><a href="/feed.xml">feed</a><button id="theme-toggle">🌓</button></p></nav></header><main id="main"><div class="blog-title"><h1>What am I up to nowadays?</h1><p><time datetime="2025-11-20">2025-11-20</time></p></div><ul> +<li>Probably, tweaking this site, and it's <a href="https://github.com/olexsmir/olexsmir.xyz">SSG</a>.</li> +<li>Forcing my self to go outside and sleep well.</li> +<li>Enjoying programming.</li> +<li>Hating russia and wanting more of електрохарчування.</li> +</ul> +</main><script>const root = document.documentElement;root.dataset.theme = localStorage.theme || 'dark';document.getElementById('theme-toggle').onclick = () => {root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';localStorage.theme = root.dataset.theme;};</script></body></html>
A posts.html

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

+<!DOCTYPE html><html lang="en"><head><title>All olexsmir's posts</title><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"><link href="https://olexsmir.xyz/feed.xml" rel="alternate" title="olexsmir's blog feed, also hi rss reader!" type="application/atom+xml"></link><link href="style.css" rel="stylesheet"></link><link href="assets/favicon.svg" rel="icon"></link><meta content="List of all blog posts on the lego." name="description"><meta content="List of all blog posts on the lego." property="og:description"><meta content="All olexsmir's posts" property="og:site_name"><meta content="All olexsmir's posts" property="og:title"><meta content="website" property="og:type"></head><body class="home"><header><nav><p><a class="visual-hidden" href="#main">Skip to content</a><a href="/">home</a><a href="/posts">posts</a><a href="/feed.xml">feed</a><button id="theme-toggle">🌓</button></p></nav></header><main id="main"><ul class="blog-posts"></ul></main><script>const root = document.documentElement;root.dataset.theme = localStorage.theme || 'dark';document.getElementById('theme-toggle').onclick = () => {root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';localStorage.theme = root.dataset.theme;};</script></body></html>
D

@@ -1,11 +0,0 @@

---- -title = What am I up to nowadays? -slug = now -date = 2025-11-20 -hidden = true ---- - -- Probably, tweaking this site, and it's [SSG](https://github.com/olexsmir/olexsmir.xyz). -- Forcing my self to go outside and sleep well. -- Enjoying programming. -- Hating russia and wanting more of електрохарчування.
A sitemap.xml

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

+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"><url><loc>https://olexsmir.xyz</loc><lastmod>2025-11-20T00:00:00+02:00</lastmod><priority>1.0</priority></url><url><loc>https://olexsmir.xyz/now</loc><lastmod>2025-11-20T00:00:00+02:00</lastmod><priority>0.80</priority></url></urlset>
A style.css

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

+#theme-toggle{all:unset;border-radius:10px;cursor:pointer;padding:4px 8px;}#theme-toggle:hover{background:rgba(0,0,0,0.09);}.blog-title h1{margin-bottom:0px;margin-top:0px;}.blog-title p{margin-top:0px;}.recent-posts{padding-top:0.5rem;}.visual-hidden:not(:focus){bottom:100%;position:absolute;}:root{--background-color:#131418;--code-background-color:#2d2d2d;--heading-color:#eaeaea;--link-color:#82aee3;--text-color:#babdc4;--text-mutted:color-mix(in srgb, var(--text-color) 70%, var(--background-color));}[data-theme='light']{--background-color:#fff;--code-background-color:#d8dbe2;--heading-color:#0d122b;--link-color:#003fff;--text-color:#434648;--text-mutted:color-mix(in srgb, var(--text-color) 70%, var(--background-color));}a{color:var(--link-color);cursor:pointer;text-decoration:none;}a:hover{text-decoration:underline;}body{background-color:var(--background-color);color:var(--text-color);font-family:system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;font-size:1rem;letter-spacing:0.005em;line-height:1.5;margin:0 auto;max-width:66ch;overflow-wrap:anywhere;padding:0.6rem 1.7rem 3.1rem;}button{cursor:pointer;margin:0;}code{background-color:var(--code-background-color);font-family:monospace;padding:0 0.3em;white-space:pre-wrap;}h1, h2, h3, h4, h5, h6{color:var(--heading-color);font-family:var(--font-main);}header{padding-bottom:0.3rem;}hr{border:0;border-top:1px dashed;}i{font-style:italic;}img{display:block;justify-self:center;max-width:100%;}main{line-height:1.6;padding-top:1.3rem;}main a{color:var(--text-color);text-decoration:underline;}main a:hover{color:var(--link-color);}nav a{margin-right:0.9rem;}nav p{margin-bottom:0px;}pre{border-radius:6px;padding:10px;}pre code{border-radius:0;padding:0;}strong, b{color:var(--heading-color);}table{width:100%;}time{color:var(--text-mutted);font-family:monospace;font-size:0.95rem;font-style:normal;}ul.blog-posts{list-style-type:none;padding:unset;}ul.blog-posts a{color:var(--link-color);text-decoration:none;}ul.blog-posts a:hover{text-decoration:underline;}ul.blog-posts a:visited{color:var(--link-color);}ul.blog-posts li{display:flex;}ul.blog-posts li span{flex:0 0 130px;}
D

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

-column_width = 100 -indent_type = "Spaces" -indent_width = 2 -no_call_parentheses = true