💾 Archived View for tilde.town › ~mio › log › 2024-06-01-tilde30.gmi captured on 2024-08-18 at 17:41:40. Gemini links have been rewritten to link to archived content
View Raw
More Information
⬅️ Previous capture (2024-07-09)
🚧 View Differences
-=-=-=-=-=-=-
---
date: 2024-05-31T19:30:46Z
update: 2024-06-30T03:39:51Z
---
tilde30, June 1-30 2024
Tilde 30 is a event that involves picking a project or series of related activities and doing them over the course of thirty days. The main premise is to set weekly milestones or goals to complete them. More details:
tilde30.txt
Townies can also see a list of what people will be doing in the "tilde30!" thread on bbj, the town bulletin board. Thanks to ~elly for organising the event.
Project
My tilde30 mini-project will be a static gallery gemerator. Given a folder of images and metadata text files, write a CLI utility to generate static pages for a simple web gallery.
Milestones
Week 1: learn enough of a programming language to make mischief
Week 2: model config settings, parse sample config and image information
Week 3: write logic to source thumbnails, generate HTML from a template
Week 4: template sample gallery theme
Stretch goal: add HTML/CSS to the forforthcoming stats feature in Katalogo. Katalogo is a webring server by ~durrendal. Timing will depend on feature progress and remaining time. Templating can be done more effectively when the backend returns the sample data to be displayed.
Katalogo
Background
When last checked several years ago, most web gallery generators were either written in PHP, content management systems that handled embedding multimedia, or application servers that would not be easily made publicly accessible outside of ~town. Static HTML pages were less resource-intensive and could adequately display small image collections. The closest static site generators with a gallery component were Sigal and Nikola. Sigal was centered around generating gallery pages and image thumbnails, configuration and theming were straightforward, though it did not handle adding other pages that were not part of a gallery. Nikola had a gallery view alongside regular pages and other features, but the image lightbox view did not support multi-paragraph captions. My fix at the time was to combine the gallery built through Sigal with a shell script to output extra text-only pages, both sharing the same HTML theme. The arrangement worked, if clunkily.
Sigal — Simple Static Gallery Generator
Nikola — Static Site Generator
One or two months ago, someone asked in ~town chat if there was a gallery generator installed. By then had written a single-page generator that could serve as a gallery feature or subsite, but the idea of producing the gallery applet that should have existed years ago lingered. In twenty-nine days, the folly of this idea will be revealed.
znic: 1-page webzine generator
Update 2024-06-23: found imgram, a shell script HTML gallery generator while browsing a cluster of websites adjacent to the Old Computer Challenge gateway. It is closer to an imageboard without comments, having a Tumblr-like layout with support for tags and pagination. The author, prahou, kindly pointed me to the source (link below). A good option with a RSS feed.
imgram
Old Computer Challenge
Timeline
The sections below will be updated through the month.
Start: 2024-06-01
- Set up Go dev tools
- Started Go crash course, ran first "hello world" Go program
Week 1: 2024-06-08
- Wrote functions to load and output a JSON config
- Wrote an image thumbnailer
- Wrote function to generate an HTML page of thumbnails based on config
- Week 1 milestone completed, week 2 and 3 milestones in progress
Week 2: 2024-06-15
- Refactored single script into modules
- Wrote functions to import image descriptions
- Wrote functions to generate HTML pages of images with descriptions
- Wrote a function to generate an index page in two modes
- Updated simple HTML templates used in page generation
- Week 2 and 3 milestones completed, week 4 milestone in progress
Week 3: 2024-06-22
- Added navigation links to the image page template
- Added CSS lightbox view to the image set and index page templates
- Week 4 milestone half-complete
Week 4: 2024-06-29
- Included a plain theme template
- Wrote usage instructions
- Week 4 milestone completed
- Stretch goal change: added project demo page
End: 2024-06-30
- Source release: https://git.tilde.town/mio/lamium
- Project demo page: https://tilde.town/~mio/lamium
- Townies can get a prebuilt binary at: ~mio/bin/lm
Updates
Day 0
- Wrote a Nim script to copy the timeline summary from this gemtext to the local `~/.plan` and `~/.project` files in ~town, in accordance with the event guidelines. It is added as a remote SSH command to my gemwriter capsule config, which asks the utility to refresh the dotfiles on publish or update. The script helps me log from Gemini while synchronising the `.project` file. It should work with other plain text formats beside Gemini with a few config adjustments.
- One caveat: the script will overwrite the files each update, which is unsuitable for people who have other content inside the files unrelated to the event. However, it is possible to modify the script to keep a template copy of both files and insert at the position indicated by a placeholder. My `.plan` and `.project` files would otherwise remain unused, and the script in its current state is adequate for my use case. Less time spent on ancillary tooling, more time available to do the project. Others are welcome to modify it for their needs if they wish.
- The source is available at the link below. Townies can also copy the pre-compiled executable currently at `~mio/bin/t30` to their own `bin/` and run it from there. Example config for this gemtext at `~mio/.t30.json`.
t30.nim
Start / Day 1
- Took up this little project as an opprtunity to become acquainted with a new programming language. No hard criteria — a modern C-style language, a standard library with good basic coverage, yet resource-efficient and compiles packages with a modest amount of resources. Language candidates: Go, Rust, Zig.
- Lost some appetite for Rust after watching cargo fetch 600 MB of packages for a 4 MB package that runs TAP tests in Python. Building dependencies seemed slower with a single core than another compiled language, and the dependencies were probably already cached from the first time. The language server, rust-analyzer, apparently does not support 32-bit architectures and was unavailable in the distribution package repos for my armv7 device. A language server is not required to write code, more a general indicator of the state of infrastructure and tooling around a language.
- Initial impression of Zig was positive, although the language is still experimental. The repo package was outdated and had failed to build on 32-bit architectures. However, the pre-packaged binaries from the official website ran on my device, which sadly seldom occurs on a musl C + armv7 system. The language server, zls, also built easily from source without issue. Helix (a text editor) kept freezing on file save while it ran `zig fmt [file]`. Could not reproduce the issue running the command standalone after the first time it hung there. No async library in recent versions, but not a requirement for the project. Would take a closer look at Zig in a future project.
- Trying Go this time. As it will be a utility to be mainly used at ~town, it is probably better to write it in a language more widely used in ~town so others can fork and adapt to their needs. The language server is already packaged in the repos, no tooling issues.
# Check Helix's language server detection and grammar support.
# Enable Go in the use-grammars list in ~/.config/helix/languages.toml.
hx --health go
hx --grammar fetch
hx --grammar build
# Install Go compiler and language server.
apk add go gopls
- Started reading the Go by Example introduction as a crash course.
Go by Example
Screenshot of the Helix editor with two vertical panes, the left containing sample code and a gopls tooltip about the `Println()` function spanning both panes, and a gemtext file open on the right
Week 1
- Started data modelling of the configuration file. Used JSON for config file format and output an initial sample configuration. JSON is supported in the standard library.
- Started CLI argument parsing and options handling.
- Wrote a function to resize a copy of an image to the supplied dimensions, which will be used for thumbnails.
- Wrote a function to generate thumbnails of images in a directory based on the resize image function. There are five modes. Below is a description of each mode and how an image might be scaled if width and height are set to 200px and 100px respectively:
1. Maximum size: images are the maximum size at the longer side. An image in landscape would be 200px in width and less in height, and an image in portrait a height of 200px, less in width. A square image would have a width and height of 200px.
2. Minimum size: images are the minimum size at the shorter side. An image in landscape would be 100px in height and more in width, and an image in portrait a width of 100px, more in height. A square image would have a width and height of 100px.
3. Width: images have the same width. An image in landscape would be 200px wide, less in height, and an image in portrait would be 200px wide, more in height. A square image would have a width and height of 200px.
4. Height: images have the same height. An image in landscape would be 100px high and wider than 100px, and an image in portrait would be 100px high, and narrower than 100px. A square image would have a width and height of 100px.
5. None: images are sized at a percentage of the original image regardless of set width and height. The current default is 50% of the original image dimensions, e.g. a 600 x 800px source image yields a new 300 x 400px image.
- Wrote a preliminary function to output an HTML page of thumbnails based on config file settings and a bare HTML template.
A plain HTML page with 2 rows of 6 square thumbnails resized from illustrations
Lesson recap:
- JSON marshalling and unmarshalling convert to and from bytes instead of directly to string possibly because strings are read-only slices of bytes and most string operations require copying, which requires more resources and garbage collection. Therefore it may be more efficient to process as bytes which have read-write capabilities.
Advantages to JSON as []byte instead of string
- When unmarshalling to a struct type, the struct's fields need to begin in an uppercase character to denote they are exported, because the unmarshalling uses the `reflect` package which works on exported fields. If the fields are unexported, the result will be an empty JSON object, `{}`.
- Maps are collections of key-value pairs, like dictionaries in other languages. Limitations of maps:
1. Unsorted: the spec does not include sorted maps as a requirement to allow for different types of maps in implementation. Avoid using maps alone for menus or scenarios where the elements' order of appearance is important. Use with a slice if needing a sorted collection.
2. Not concurrency-safe on its own: use with a synchronisation library to read and write to them at the same time.
Go maps in action
- There are two official packages named `draw`, one in the standard library and the other a drop-in replacement for the former. Both package pages point to the same introduction article but one of them is missing key features, e.g. the standard library `draw` does not have the `Copy` function. In the event of an `undefined: draw.[...]` error when attempting to call one of the scaling variables or `Scale` function, check the import statement to ensure the correct package is imported — `golang.org/x/image/draw`, not `image/draw`, which has fewer functions.
image/draw (standard library)
golang.org/x/image/draw
- To install packages, the project directory needs a `go.mod` file because `go get` no longer installs packages outside a module. Another thing to note is if the project directory is not within `GOPATH` (by default `$HOME/go` or check the current value with `go env GOPATH`), either move it, or pass in a module or project directory name for the file init command to work.
# Generate a go.mod file.
go mod init [module]
# Fetch the package.
go get golang.org/x/image/draw
# Add import path to source.
import "golang.org/x/image/draw"
- Optional parameters, method overloading and variant types are unsupported. Workarounds to optional parameters:
1. Variadic arguments (`func foo(param ...string)` syntax), which allow for empty slices including not passing a value. Depending on use case, it can act like an optional parameter and is fine as long as the function only acts on the same number of elements as present in the slice. It may yield unexpected results (additional elements are ignored) or leave cruft unchecked if more elements are passed in than the function handles, as the compiler would not warn of a mismatch.
2. Place default values in a struct and convert the function to a method on the struct.
3. Pass in a placeholder value and reset it to a default value inside the function.
4. Write function variants for different sets of arguments.
Default value in Go's method
Proposal: add limits to variadic definition
Why does Go not support overloading of methods and operators?
- Convert integers to floats before doing division. Example: `float32(600/800)` and `float32(600)/float32(800)` are different. The first results in 0, the other 0.75.
- There are no copy directory or copy file functions yet. Go 1.23 will probably introduce os.CopyFS to copy files. In the meantime, a workaround is to iterate through a list of items from `os.ReadDir` and apply `os.Mkdir`, `os.Read` and `os.Write` to copy each file's contents accordingly.
golang/go os: add CopyFS
3 ways to copy files in Go
Week 2
- Modified the HTML output function to allow showing multiple image sets on the same page.
- To speed up output of files, the generating function will only copy and resize images if they do not already exist in the output directory. To reset the thumbnails, e.g. after changing the thumbnail size in the config file, the directory containing the images in question within the output directory should be manually removed.
- Modified the file list fetching function to sort by name or modification time.
- Wrote a preliminary function to load image descriptions if provided and insert them into their corresponding image pages.
- Refactored the script to date, split the single file into modules by functionality. A tentative list of exported modules:
1. `image`: functions for calculating image crop size and resizing.
2. `template`: functions to generate gallery pages.
3. `util`: a mix of supplementary functions unavailable in the standard library, e.g. multiple substring replacement for strings, boilerplate for copying files, sorted file lists.
- Fixed a few bugs in importing image descriptions and output directory paths.
- Filtered HTML and text descriptions to allow a small subset of markup.
- Refactored the page generation functions, a function for each of the three locations: the top-level or index page, the set page for each set of images, and individual image pages.
- The index page supports two modes: a sets mode to manually specify which sets to display on the page, and a text mode which loads a text or HTML description. The set page also supports a description file.
- Revised the template variables and organised them by page template type.
A plain HTML page with two sets of image thumbnails
Lesson recap:
- Sorting strings: `sort.Strings`, which as of Go 1.22 is an alias for `slices.Sort`. `sort` does not have a string reverse sort, its `Sort` and `Reverse` are for interfaces — use `slices.Reverse`.
- Printing strings in title case: `strings.Title` was deprecated in Go 1.18 and the documentation recommends using the `golang.org/x/text/cases` package.
strings.Title (deprecated)
cases.Title
- If the codebase has functions spread across multiple files in the same directory and package name, and running `go run main.go` gives an error like
`# command-line-arguments ./main.go:8:2: undefined: [function]`, change the run command to include all Go files in the current directory: `go run . [args]`
Week 3
- Added basic navigation links to the image page template. Each image description page may include "next", "previous" and "top" links to more easily browse through a set of images. A bug was found wherein the next/previous links were incorrect in 1-2 of the image pages, to be fixed later.
- Included CSS files in the album output function.
- Began applying basic CSS styling to the set page.
- Added a CSS lightbox view, which loaded individual image pages on top of the thumbnails. This allowed for viewing image details, then hiding the overlay again to select another thumbnail. The feature still needs minor adjustments at the moment — the navigation links on the page template should hide the "top" link since there is already another link to hide the overlay and return to the thumbnails.
A page with the image set title and description at the top, followed by 3 rows of 4 thumbnails centered on the page
Same page showing a lightbox view, with a translucent white layer over the visible area and a larger image of a paper tilde on top, the title and short 1-line description below the image
Lesson recap:
- When attempting to convert a single digit `int` to a `string` by passing it into the `string` function, the linter will warn that the `conversion from int to string yields a string of one rune, not a string of digit (did you mean fmt.Sprint(x)?)`. In a future version this will become an error. Workaround: convert using `strconv.Itoa()`.
cmd/vet: don't complain about int to string conversion
How to convert an int value to string
Week 4
- Made lightbox mode responsive.
- Wrote a brief 1-page document with usage instructions.
1. CSS styles not copied when the `make` option is invoked the first time.
2. Top-level index.html being output to the source directory top-level when the config value is empty.
3. Runtime error when there is only one image in a set. Also hid the lightbox navigation links in this case.
4. Check for duplicate index page results in the index page being renamed when the set page has a description file.
5. Image descriptions not displayed after adjusting the description file path check.
- Embedded a plain sample theme with the executable to make it easier for users to get started.
- Added a project demo page.
- Backfilled doc comments for functions.
Lesson recap:
- `strings.Replace` will insert the replacement substring at the beginning of the origin string if there are no substring matches. A workaround to prevent accidental insertion is to check whether the origin string contains the search substring before replacing.
- For the `embed` package, the `//go:embed` directive cannot be used inside a function. The linter will warn even though the statement does precede a `var` declaration inside the function. Apparently embedding locally was removed due to poor interaction with byte slices.
embed: remove support for embedding directives on local variables
Bundling static resources
embed package
- Two things to be aware of with paths in the `//go:embed` directive:
1. Variables cannot be used in the `//go:embed` directive. Oddly, the linter did not notify of an error.
# Incorrect example.
//go:embed filepath.Join(SampleThemeDir, "*")
# Error on compile.
template/template.go:196:3: invalid quoted string in //go:embed: )
template/template.go:196:3: usage: //go:embed pattern...
# Corrected example.
//go:embed themes/nettle/*
2. The path is relative to the package root directory. If the directive is invoked from a subdirectory and the files are in another adjacent subdirectory, either move the files to a location inside the same subdirectory, or create a `config.go` file and embed the files from there.
How to Use //go:embed to embed static files in CLI
End
Source repo
Project demo page
- Pushed changes over HTTPS as `git [push|pull]` over SSH did not work for me at the moment. The error was `exec request failed on channel 0 [...] fatal: Could not read from remote repository.` No change to SSH keys or config for git.tilde.town. A quick web search indicated it could be an app server issue from the git user spawning too many processes than allowed in `ulimit`.
Problems with fetching repos via ssh after updated from 16.1.5 to 16.3.3
- Left a build in town at `~mio/bin/lm` in case anyone might be interested in trying it.