Skip to content

nicholaswmin/nix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

is a single-file static-site generator in 150 lines of ruby [WIP]

It's distinguishing characteristic is that it's archicture
& system design philosophy (over)emphasizes simplicity to an unsual degree,
even more so than program correctness.

Read more:

Instead of installing X/Y publishing framework on your system,
it inverts the process by embedding the "framework" in the site itself.

  • create a repo & drop nix.rb into it
  • run nix.rb --init
  • add posts & pages as markdown
  • git push

... which publishes automatically on Github Pages.

todo

  • unit tests
  • data should be pruned outside of this readme
  • code docs

anyone with repository access can edit/publish the site by simply cloning the repo,
add/edit posts and re-pushing to main.

Usage: nix [options]

nix  --init            create sample blog
nix  --build           build static HTML
nix  --watch           rebuild on file change

todo

  • docs
  • unit tests

Publish a site

  1. create a Github repo with enabled pages.
  2. drop the nix.rb file in it.
  3. Run ruby nix.rb --init to generate a sample site
  4. push to main branch

... which publishes a barebones site consistent with the 1kb philosophy at: <user>.github.io/<repo>

Publish new content

  1. write markdown in posts/ and pages/
  2. push to main branch

quick start

Say you have a repo with the following structure:

repo
┣━nix.rb
┗━README.md

generate a minimal sample blog:

ruby nix.rb --init

write posts

  • add markdown posts in /posts
  • add markdown pages in /pages
  • add images, videos, CSS in /public

posts include syntax highlighting, just wrap them in code fences (```) as usual:

const hello = 'world'

moving on..

build to static HTML, at /build

ruby nix.rb --build
# build ok!

or even better:

rebuild automatically on file change

ruby nix.rb --watch

publish it

Just push to main.

An autogenerated Github workflow will run nix --build to compile
everything once-more & deploy at: https://username.github.io/repo-name.

/build directory is recompiled on push so you can safely .gitignore it entirely.

post format

just make sure each page or post has:

  • an h1 title on the 1st line
  • an empty line on the 2nd line
  • a <date> on the 3rd, ie. 2024-10-18

the rest is up to you.

example:

# Some pretentious post title
 
2024-12-20

> A sentence should contain no unnecessary words, a paragraph no unnecessary 
> sentences, for the same reason that a drawing should have no unnecessary lines 
> and a machine no unnecessary parts. This requires not that the writer make all 
> his sentences short, or that he avoid all detail and treat his subjects only 
> in outline, but that every word tell

The Elements of Style by William Strunk Jr (1918) ...

get nix

nix is just a single-file; it's max 150 lines of code & it includes everything necessary to develop and publish a ridicously minimal yet functional blog site.

grab it from this repo directly, or just curl it:

curl -O https://raw.githubusercontent.com/nicholaswmin/nix/main/nix.rb

apart from Ruby 3.3 there's nothing to install; nor any commands to run. You don't need to run gem install. It's not available as a gem either;

This is intentional

Quirks

Asset paths

Don't use absolute paths to reference assets.

Github Pages has a well-known quirk of serving from a non-root path, applicable to all static-site generators.

this won't work:

<img src="/public/felix.svg"></img>
<img src="/felix.svg"></img>

use relative paths instead:

<img src="../../felix.svg"></img>

even better, use {{root_url)}:

<img src="{{root_path}}/felix.svg"></img>
<!-- auto expands to <img src="../../felix.svg"></img> -->

it's rewritten automatically & resolves to the correct root regardless of the
page position, so it's less error-prone than manually writing relative paths.

same in markdown:

[1]: {{root_url}}felix.svg

root resolution in ruby

in case you're extending nix:

root_url is also available as a method which has the same effect:

root_url('felix.svg')
# ../../felix.svg

more context:

class CustomPage < HTMLPage
  # omitted ...

  def render(ctx)
    super + 
      "<link rel=\"stylesheet\" href=\"#{root_url('highlight.css')}\"><link>"
  end
end

Wheres the rest of it?

This project is actually part of a weekly workshop I was invited to do in an SME. It's an actual project that you can use but it's primary purpose was illustrative.

The idea behind it closely mimics wruby, a static-site generator that generates sites that are under 1kb.

While the whole thing looks more like a code-golfing joke than anything substantial, it's philosophy is based around serious ideas that emerged in MIT and Berkley around the 80s(?), regarding software architecture design.

Despite their similarities, under the hood wruby is written entirely procedurally.

There is a clear lack of architecture and you can more or less describe it as a cooking recipe.

What I've done is take wruby and rewrite it's core ideas in an Object-Oriented paradigm; in an effort to make the code more "modern" and "extensible"; in effect I've proved the entire point of the argument, the tendency to introduce unnecessary complexity on our own.

nix is using a lot of ideas supposedly considered "Best Practices". Data is moved around in a functional manner; the API is implemented in a fluent prose with clear hierarchical organisation into classes/types.

Additionally, there's a clear and intentional Separation of Concerns between persistence code and logic code; the purpose of this is to make it amerable to unit-testing.

wruby has absolutely none of the above.

it's really just a bunch of functions glued together.
It's not possible to extend it without pulling out your hair but nor is there any indication that it's purpose was to be extensible.

wruby is actually one of the few projects that I sat through reading it's entire source-code without getting bored or distracted, because it is just that simple.

Both projects are written in Ruby.

Both projects are based on material from the following essays:

From the Unix-Haters handbook
Simson Garfinkel

[...] Literature that Unix succeeded because of its technical superiority. This is not true. Unix was evolutionarily superior to its competitors, but not technically superior. Unix became a commercial success because it was a virus. Its sole evolutionary advantage was its small size, simple design, and resulting portability.

The New Jersey style of software architecture design
vs the MIT/Stanford approach

Richard P. Gabriel, 1991

[... ] I and just about every designer of Common Lisp and CLOS has had extreme exposure to the MIT/Stanford style of design. The essence of this style can be captured by the phrase The Right Thing. To such a designer it is important to get all of the following characteristics right:

Simplicity

the design must be simple, both in implementation and interface. It is more important for the interface to be simple than the implementation.

Correctness

The design must be correct in all observable aspects. Incorrectness is simply not allowed.

Consistency

The design must not be inconsistent. A design is allowed to be slightly less simple and less complete to avoid inconsistency. Consistency is as important as correctness.

Completeness

The design must cover as many important situations as is practical. All reasonably expected cases must be covered. Simplicity is not allowed to overly reduce completeness.


[...] The Worse is Better philosophy is only slightly different:

Simplicity

The design must be simple, both in implementation and interface.
It is more important for the implementation to be simple than the interface. Simplicity is the most important consideration in a design.

Correctness

The design must be correct in all observable aspects. It is slightly better to be simple than correct.

Consistency

The design must not be overly inconsistent.
Consistency can be sacrificed for simplicity in some cases, but it is better to drop those parts of the design that deal with less common circumstances than to introduce either implementational complexity or inconsistency.nt as correctness.

Completeness

Completeness -- the design must cover as many important situations as is practical. All reasonably expected cases should be covered. Completeness can be sacrificed in favor of any other quality. In fact, compzleteness must be sacrificed whenever implementation simplicity is jeopardized. Consistency can be sacrificed to achieve completeness if simplicity is retained; especially worthless is consistency of interface.

I have intentionally caricatured the worse-is-better philosophy to convince you that it is obviously a bad philosophy and that the New Jersey approach is a bad approach.

[...] However, I believe that worse-is-better, even in its strawman form, has better survival characteristics than the-right-thing, and that the New Jersey approach when used for software is a better approach than the MIT approach[...]