diff --git a/README.md b/README.md index b8baa3c..d23f4cb 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,9 @@ $ docker run --rm -it --init -w /opt -v $PWD:/opt -p 8080:8080 sitegen-openresty ### visit `localhost:8080` in web browser + +## todo + +* treesitter highlighting for moonscript +* penlight library +* sitegen plugins diff --git a/app.css b/app.css new file mode 100644 index 0000000..51ed45d --- /dev/null +++ b/app.css @@ -0,0 +1,734 @@ +/*@import "tailwindcss/base";*/ + +/*@import "tailwindcss/components";*/ + +/*@import "tailwindcss/utilities";*/ + +/* This file is for your main application CSS */ + +/*styles.css*/ + +/*tones #ff2a2a*/ + +/*ff2a2a*/ + +/*ef3535*/ + +/*df3f3f*/ + +/*cf4a4a*/ + +/*bf5555*/ + +/*af5f5f*/ + +/*9f6a6a*/ + +/*8f7575*/ + +/*tints*/ + +/*.color-1 {color: #ff2a2a;}*/ + +/*.color-2 {color: #ff4545;}*/ + +/*.color-3 {color: #ff5f5f;}*/ + +/*.color-4 {color: #ff7a7a;}*/ + +/*.color-5 {color: #ff9595;}*/ + +/*.color-6 {color: #ffafaf;}*/ + +/*.color-7 {color: #ffcaca;}*/ + +/*.color-8 {color: #ffe4e4;}*/ + +a[href]:link { + color: #ff2a2a; + text-decoration: none; +} + +a[href]:visited { + color: dodgerblue; + color: skyblue; + color: #FF5733; + color: #900C3F; + /*tones*/ + /*color: #ff2a2a;*/ + /*color: #ef3535;*/ + /*color: #df3f3f;*/ + color: #cf4a4a; + /*color: #bf5555;*/ + /*color: #af5f5f;*/ + /*color: #9f6a6a;*/ + /*color: #8f7575;*/ + /*tints*/ + /*color: #ff2a2a;*/ + /*color: #ff4545;*/ + /*color: #ff5f5f;*/ + /*color: #ff7a7a;*/ + /*color: #ff9595;*/ + /*color: #ffafaf;*/ + /*color: #ffcaca;*/ + /*color: #ffe4e4;*/ +} + +a[href]:hover { + text-decoration: underline; + color: lightblue; + color: #FF5733; + /*tints*/ + /*color: #ff2a2a;*/ + /*color: #ff4545;*/ + /*color: #ff5f5f;*/ + color: #ff7a7a; + /*color: #ff9595;*/ + /*color: #ffafaf;*/ + /*color: #ffcaca;*/ + /*color: #ffe4e4;*/ +} + +a[href]:active { + /*tints*/ + /*color: #ff2a2a;*/ + /*color: #ff4545;*/ + /*color: #ff5f5f;*/ + /*color: #ff7a7a;*/ + color: #ff9595; + /*color: #ffafaf;*/ + /*color: #ffcaca;*/ + /*color: #ffe4e4;*/ +} + +header a[href]:visited { + color: #ff2a2a; +} + +/*header a[href]:hover {*/ + +/* color: #FF5733;*/ + +/*}*/ + +/**/ + +/*header a[href]:active {*/ + +/* color: #FFC300;*/ + +/*}*/ + +.app-source-code-link { + font-size: smaller; + font-weight: normal; + margin-left: 0.5em; + float: right; +} + +body { + font: 0.95em/1.692307em 'Bitter', Georgia, 'Times New Roman', Times, serif; + color: #24292e; + display: flex; + margin: 0 auto; + padding: 0; + flex-wrap: wrap; + max-width: 1160px; + flex-flow: column; +} + +header { + padding: 0 1em; + text-align: center; + flex-grow: 1; +} + +header > dl > dt > a[rel="author"] { + font-weight: bold; + font-size: x-large; +} + +nav { + font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; +} + +header > dl > dd { + font-family: Monaco, monospace, Courier, "Courier New"; + margin: 0; + font-size: smaller; +} + +header > dl { + margin-bottom: 3em; +} + +nav > ul > li { + text-transform: uppercase; +} + +main { + text-align: center; + flex-grow: 1; + padding: 0 1em; +} + +main * { + text-align: left; + margin-left: auto; + margin-right: auto; + box-sizing: border-box; +} + +main > div { + display: inline-block; +} + +section, h2 { + max-width: 650px; + box-sizing: border-box; +} + +section { + padding-bottom: 1em; +} + +section + section { + padding-bottom: 1em; +} + +section:first-child h3 { + margin-top: 0.83em; +} + +section:last-child { + padding-bottom: 0; +} + +section > h3 { + margin-top: 0; + margin-bottom: 0; +} + +section + section > h3 { + margin-top: 1em; +} + +section > time { + font-weight: bold; + font-size: smaller; + /*color: #99C300;*/ + /*color: #FFC300;*/ + /*color: #ffca2a;*/ + /*color: #b7df3f;*/ + /*color: #aecf4a;*/ + /*color: #a5bf55;*/ + /*color: #9baf5f;*/ + /*color: #caff2a;*/ + /*color: #b1df25;*/ + color: #98bf1f; + /*color: #dfb125;*/ + /*color: #bf981f;*/ +} + +section p { + margin-top: 0.4em; +} + +article > * { + max-width: 600px; + margin-left: auto; + margin-right: auto; + box-sizing: border-box; +} + +article > time { + display: block; + text-align: center; + margin-bottom: 1.5em; +} + +section > time, article > time { + font-family: sans-serif; +} + +article > h2:first-child { + text-align: center; +} + +article h2, article h3, article h4, article h5 { + margin-top: 2em; +} + +.info { + border: 1px solid slategray; + border-radius: 8px; + padding: 0 1em; + background-color: #ddf0f0; + font-family: sans-serif; + color: darkslategray; +} + +.info > p:first-child:before { + content: "ⓘ"; + float: left; + font-size: xx-large; + color: slategray; + margin-right: 0.25em; +} + +.warning { + border: 1px solid orange; + border-radius: 8px; + padding: 0 1em; + background-color: beige; + font-family: sans-serif; + color: darkslategray; +} + +.warning > p:first-child:before { + content: "⚠"; + float: left; + font-size: xx-large; + color: orange; + margin-right: 0.25em; +} + +figure { + text-align: center; +} + +figure img { + max-width: 100%; +} + +figure figcaption { + text-align: center; +} + +pre { + background-color: aliceblue; + padding: 1em; + border: 1px solid lightgray; + border-radius: 3px; + overflow: auto; +} + +:not(pre) > code { + background-color: rgb(230, 232, 245); +} + +.code-filename-label, .filename-for-code-block > p { + font-weight: bold; + margin-bottom: 0; +} + +.code-filename-label code, .filename-for-code-block code { + background-color: transparent; +} + +.code-filename-label + pre, +.code-filename-label + div.sourceCode, +.filename-for-code-block + pre, +.filename-for-code-block + div.sourceCode { + margin-top: 0; +} + +code { + font-size: 0.85em; + font-family: Monaco, monospace, Courier, "Courier New"; +} + +li p img { + float: left; + margin-bottom: 40px; +} + +li, p { + clear: both; +} + +address { + text-align: center; + font-style: normal; +} + +blockquote { + border-left: 5px solid #ccc; + padding: 0.1em 0; + padding-left: 1em; +} + +footer { + padding: 1em; + font-size: smaller; + font-family: sans-serif; + margin-top: 0; + white-space: nowrap; + color: lightgray; +} + +footer ul, nav ul { + margin: 0; + padding-left: 1em; + list-style-type: none; +} + +.read-post-link { + white-space: nowrap; +} + +/*@media (max-width: 1100px) {*/ + +footer { + flex-basis: 100%; + text-align: center; + padding-top: 3em; + white-space: normal; +} + +footer ul { + list-style: none; + margin-left: 0; + padding-left: 0; +} + +footer li, footer ul { + display: inline; +} + +footer li::after { + content: ", "; +} + +footer li:last-child::after { + content: ""; +} + +/*}*/ + +/*@media (max-width: 950px) {*/ + +nav ul { + list-style: none; + margin-left: 0; + padding-left: 0; +} + +nav li, nav ul { + display: inline; +} + +nav li + li { + margin-left: 0.25em; +} + +.container { + white-space: normal; +} + +header { + padding-bottom: 1em; + margin-bottom: 1em; +} + +header > a[rel="author"] { + margin-top: 15px; +} + +section:first-child h2 { + margin-top: 0; +} + +address { + text-align: left; +} + +header > dl { + margin-bottom: 1em; + line-height: 1.2em; +} + +/*}*/ + +table > caption { + text-align: center; + font-weight: bold; + font-variant-caps: small-caps; +} + +/*highlighting.css*/ + +pre > code.sourceCode { + white-space: pre; + position: relative; +} + +pre > code.sourceCode > span { + display: inline-block; + line-height: 1.25; +} + +pre > code.sourceCode > span:empty { + height: 1.2em; +} + +code.sourceCode > span { + color: inherit; + text-decoration: inherit; +} + +div.sourceCode { + margin: 1em auto; +} + +pre.sourceCode { + margin: 0; +} + +@media screen { + div.sourceCode { + overflow: auto; + } +} + +@media print { + pre > code.sourceCode { + white-space: pre-wrap; + } + + pre > code.sourceCode > span { + text-indent: -5em; + padding-left: 5em; + } +} + +pre.numberSource code + { + counter-reset: source-line 0; +} + +pre.numberSource code > span + { + position: relative; + left: -4em; + counter-increment: source-line; +} + +pre.numberSource code > span > a:first-child::before + { + content: counter(source-line); + position: relative; + left: -1em; + text-align: right; + vertical-align: baseline; + border: none; + display: inline-block; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + padding: 0 4px; + width: 4em; + color: #aaaaaa; +} + +pre.numberSource { + margin-left: 3em; + border-left: 1px solid #aaaaaa; + padding-left: 4px; +} + +div.sourceCode + { +} + +@media screen { + pre > code.sourceCode > span > a:first-child::before { + text-decoration: underline; + } +} + +code span.al { + color: #ff0000; + font-weight: bold; +} + +/* Alert */ + +code span.an { + color: #60a0b0; + font-weight: bold; + font-style: italic; +} + +/* Annotation */ + +code span.at { + color: #7d9029; +} + +/* Attribute */ + +code span.bn { + color: #40a070; +} + +/* BaseN */ + +code span.bu { +} + +/* BuiltIn */ + +code span.cf { + color: #007020; + font-weight: bold; +} + +/* ControlFlow */ + +code span.ch { + color: #4070a0; +} + +/* Char */ + +code span.cn { + color: #880000; +} + +/* Constant */ + +code span.co { + color: #60a0b0; + font-style: italic; +} + +/* Comment */ + +code span.cv { + color: #60a0b0; + font-weight: bold; + font-style: italic; +} + +/* CommentVar */ + +code span.do { + color: #ba2121; + font-style: italic; +} + +/* Documentation */ + +code span.dt { + color: #902000; +} + +/* DataType */ + +code span.dv { + color: #40a070; +} + +/* DecVal */ + +code span.er { + color: #ff0000; + font-weight: bold; +} + +/* Error */ + +code span.ex { +} + +/* Extension */ + +code span.fl { + color: #40a070; +} + +/* Float */ + +code span.fu { + color: #06287e; +} + +/* Function */ + +code span.im { +} + +/* Import */ + +code span.in { + color: #60a0b0; + font-weight: bold; + font-style: italic; +} + +/* Information */ + +code span.kw { + color: #007020; + font-weight: bold; +} + +/* Keyword */ + +code span.op { + color: #666666; +} + +/* Operator */ + +code span.ot { + color: #007020; +} + +/* Other */ + +code span.pp { + color: #bc7a00; +} + +/* Preprocessor */ + +code span.sc { + color: #4070a0; +} + +/* SpecialChar */ + +code span.ss { + color: #bb6688; +} + +/* SpecialString */ + +code span.st { + color: #4070a0; +} + +/* String */ + +code span.va { + color: #19177c; +} + +/* Variable */ + +code span.vs { + color: #4070a0; +} + +/* VerbatimString */ + +code span.wa { + color: #60a0b0; + font-weight: bold; + font-style: italic; +} + +/* Warning */ diff --git a/conf/mime.types b/conf/mime.types new file mode 100644 index 0000000..89be9a4 --- /dev/null +++ b/conf/mime.types @@ -0,0 +1,89 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/font-woff woff; + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/conf/nginx.conf b/conf/nginx.conf index 8cdaa51..46f0136 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -5,14 +5,21 @@ events { } http { server { - root /opt/www; listen 8080; + + include mime.types; + charset utf-8; + location / { default_type text/html; # content_by_lua_block { # ngx.say("
hello, world
") # } } + + location /css { + expires 1h; + alias css; + } } } - diff --git a/docs/2023-08-03-recursively-list-all-files-in-a-directory-with-elixir.md b/docs/2023-08-03-recursively-list-all-files-in-a-directory-with-elixir.md new file mode 100644 index 0000000..469fbf4 --- /dev/null +++ b/docs/2023-08-03-recursively-list-all-files-in-a-directory-with-elixir.md @@ -0,0 +1,132 @@ +## Introduction + +We wish to print a list of all the files in a directory, ignoring files beginning with certain characters, sorted alphabetically, with the directories last. + +## Finding the files + +### Code + +Finds all files in the `path` directory recursively, ignoring any files or directories that start with any of the characters in the `@starts_with` module attribute. + +``` +defmodule Files do + @starts_with [".", "_"] + + def find(path \\ "."), do: find(path, list_contents(path)) + + defp find(dir, files) do + Enum.reduce(files, {dir, []}, fn file, {path, contents} -> + {path, path |> Path.join(file) |> update_contents(file, contents)} + end) + end + + defp list_contents(path), do: path |> File.ls!() |> ignore() + + defp update_contents(path, file, contents) do + cond do + File.regular?(path) -> [file | contents] + File.dir?(path) -> [find(path) | contents] + end + end + + defp ignore(filenames) do + Enum.reject(filenames, &(String.first(&1) in @starts_with)) + end +end +``` + +### Output + +The data structure holding the results of the file search. + +``` +iex(1)> directory_tree = Files.find("hello") +{"hello", + [ + "README.md", + {"hello/test", + [ + {"hello/test/support", ["conn_case.ex"]}, + "test_helper.exs", + {"hello/test/hello_web", + [{"hello/test/hello_web/controllers", ["error_json_test.exs"]}]} + ]}, + {"hello/lib", + [ + "hello.ex", + {"hello/lib/hello", ["application.ex"]}, + "hello_web.ex", + {"hello/lib/hello_web", + [ + {"hello/lib/hello_web/controllers", ["error_json.ex"]}, + "telemetry.ex", + "router.ex", + "endpoint.ex" + ]} + ]}, + {"hello/priv", [{"hello/priv/static", ["robots.txt", "favicon.ico"]}]}, + {"hello/config", + ["config.exs", "dev.exs", "test.exs", "prod.exs", "runtime.exs"]}, + "mix.exs" + ]} +``` + +## Sorting and printing + +### Code + +Sort alphabetically, with directories last. Downcase before comparing strings. + +``` +defmodule Paths do + def puts(dir_tree), do: dir_tree |> print() |> Enum.join("\n") |> IO.puts() + + defp print({path, contents}), do: path |> list(contents) |> List.flatten() + + defp list(path, contents) do + contents |> Enum.sort(&alpha_asc_dir_last/2) |> Enum.map(&make_path(&1, path)) + end + + defp alpha_asc_dir_last({a, _}, {b, _}), do: fmt(a) < fmt(b) + defp alpha_asc_dir_last({_, _}, _), do: false + defp alpha_asc_dir_last(_, {_, _}), do: true + defp alpha_asc_dir_last(a, b), do: fmt(a) < fmt(b) + + defp make_path(filename, path) when is_binary(filename), do: Path.join(path, filename) + defp make_path({path, contents}, _), do: list(path, contents) + + defp fmt(f), do: String.downcase(f) +end +``` + +### Output + +Print all the files sorted. + +``` +iex(2)> Paths.puts(directory_tree) +hello/mix.exs +hello/README.md +hello/config/config.exs +hello/config/dev.exs +hello/config/prod.exs +hello/config/runtime.exs +hello/config/test.exs +hello/lib/hello.ex +hello/lib/hello_web.ex +hello/lib/hello/application.ex +hello/lib/hello_web/endpoint.ex +hello/lib/hello_web/router.ex +hello/lib/hello_web/telemetry.ex +hello/lib/hello_web/controllers/error_json.ex +hello/priv/static/favicon.ico +hello/priv/static/robots.txt +hello/test/test_helper.exs +hello/test/hello_web/controllers/error_json_test.exs +hello/test/support/conn_case.ex +:ok +``` + +## Conclusion + +Trying to do this without recursion made it difficult to sort directories first. diff --git a/docs/post1.md b/docs/post1.md deleted file mode 100644 index 44adfe9..0000000 --- a/docs/post1.md +++ /dev/null @@ -1 +0,0 @@ -# post 1 diff --git a/docs/post2.md b/docs/post2.md deleted file mode 100644 index 4c1af99..0000000 --- a/docs/post2.md +++ /dev/null @@ -1 +0,0 @@ -# post 2 diff --git a/html b/html new file mode 120000 index 0000000..baf12b4 --- /dev/null +++ b/html @@ -0,0 +1 @@ +www \ No newline at end of file diff --git a/index.md b/index.md index 664aa56..8434f2b 100644 --- a/index.md +++ b/index.md @@ -1,7 +1,7 @@ { date: "Thu May 08 2025 19:41:30.000000000" - title: "index" } -Hello world! +## Posts +- [recursive]($root/posts/2023-08-03-recursively-list-all-files-in-a-directory-with-elixir.html) diff --git a/site.moon b/site.moon index bb816b7..07b1183 100644 --- a/site.moon +++ b/site.moon @@ -18,5 +18,10 @@ posts = (path=".") -> sitegen.create => @title = "Hello World" + @app_name = "stasis" + @version = "0.2.12" add "index.md" add path, target: target for path, target in pairs posts "docs" + + css = require("sitegen.tools").system_command "cat < %s > %s", "css" + build css, "index.css" diff --git a/templates/app.html.heex b/templates/app.html.heex new file mode 100644 index 0000000..7e7759f --- /dev/null +++ b/templates/app.html.heex @@ -0,0 +1,10 @@ ++ Experienced in full-stack web development with Elixir and JavaScript. +
+ +Hiring? Reach me by email.
+Elixir, JavaScript, SVG, Containers, Git, Linux
++ Questions, comments, feedback? Contact the author. +
+A virtual implementation of FASA's 1989 wargame, Battletroops, for + the browser. Suitable for single-player solo play or two-player hotseat.
+Runs entirely on the client after the initial download from the + server. Built with HTML5, SVG and JavaScript. Uses Node.js, Esbuild and + Docker for building and running dev/test servers.
++ A Hex package for installing and invoking Pandoc + ("a universal document converter"), fashioned after Phoenix's + Esbuild + and Tailwind + packages. Also included is a file system watcher, so that converted + documents are updated as soon as content changes are saved. +
+