soyweb

package module
v0.0.0-...-46af2d5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2025 License: BSD-2-Clause Imports: 16 Imported by: 0

README

soyweb

soyweb is a Go library providing ssg-go extensions.

soyweb extensions respect ssg-go quirks such as ssgignore, handling of header and footer templates, preference of source HTML files over the Markdown, file permissions, etc.

In addition to the library, it also confusingly provides an executable cmd/soyweb.

soyweb (main program)

soyweb extends Go ssg implementation for complex static site management, and is intended to be a better replacement for webtools. It could also be used to build multiple static sites at once, each with different set of ssg options.

It uses a manifest file to describe how each ssg site is going to be built. The manifest is like a config file or package.json for our site - it defines ssg options (e.g. the source and destination directories) as well as other soyweb options.

See manifest.json as example

To try soyweb, go into ./testdata and run soyweb

Synopsis:

soyweb <command> [<args>]

soyweb reads manifest(s) and apply changes specified in them. Because it is a multi-stage application, soyweb exposes these stages as 3+1 CLI subcommand:

  • Default mode

    It builds ./manifest.json with all stages.

    Due to the limitation of the CLI library, this default mode takes no arguments.

    # Build from ./manifest.json (default path)
    soyweb
    
  • soyweb build

    This subcommands build sites from one or multiple manifests.

    We can specify skip flags to build, which will make soyweb skip some particular stages during application of manifests.

    By default, soyweb build enables the soyweb index generator and all minifiers are disabled.

    Help:

    soyweb build -h
    

    Examples:

    # Build from ./manifest.json (same with default behavior)
    soyweb build
    
    # Like above, but do not generate index from marker _index.soyweb
    soyweb build --no-gen-index
    
    # Build from ./manifest.json
    # without copying files defined in manifest
    soyweb build --no-copy
    
    # Build from ./m1.json and ./m2.sjon
    soyweb build ./m1.json ./m2.json
    
    # Build from ./m1.json and ./m2.json
    # without actually building HTMLs from Markdowns
    #
    # The --no-build option also disables all soyweb build features
    # that relies on ssg options, such as minifiers and index generator
    soyweb build --no-build ./m1.json ./m2.json
    
    # Build from ./m1.json and ./m2.json
    # and minify all HTML files built from Markdown
    soyweb build ./m1.json ./m2.json --min-html
    
    # Like above, but minify all HTML files
    soyweb build ./m1.json ./m2.json --min-html --min-html-copy
    
    # Like above, but minify all HTML files and CSS files
    soyweb build ./m1.json ./m2.json --min-html --min-html-copy --min-css
    
  • soyweb clean

    Removes target files specified in the manifests' copies directive

    Help:

    soyweb clean -h
    soyweb cleanup -h
    
  • soyweb copy

    Copy files specified in the manifests' copies directive

    Help:

    soyweb copy -h
    

Other soyweb programs

Most of these programs share the same CLI flags, and the help messages can be accessible via -h or --help

In addition to the bloated executablesoyweb, soyweb (the project) also provides minimal executables to integrate into other static site pipelines:

  • minifier

    A web format minifier. It minifies a single source file and writes the minified version to different location, or all supported files under the source directory

    minifier by default minifies all known media types, but this behavior can be controlled with --no-min-{ext} flags:

    # Minify all known file extensions
    minifier some/src some/dst
    
    # Do not minify .js files
    minifier some/src some/dst --no-min-js
    
    # Do not minify .html and .css files
    minifier some/src some/dst --no-min-html --no-min-css
    
  • ssg-minifier

    A minifier-enabled version of standard ssg. Usage is like with the original ssg or ssg-go, but CLI accepts --no-min-{ext} flags just like cmd/minifier:

    # Build from some/src to some/dst, minifying every known file extension
    ssg-minifier some/src some/dst some-title some-url.com
    
    # Build from some/src to some/dst, minifying every file extension except for .json
    ssg-minifier some/src some/dst some-title some-url.com --no-min-json
    
    # Build from some/src to some/dst, minifying every file extension except for .js and .css
    ssg-minifier some/src some/dst some-title some-url.com --no-min-js --no-min-css
    

soyweb manifest

A soyweb manifest is a JSON file describing all ssg-go and soyweb options for soyweb sites. It defines soyweb sites as a JSON map object, accessed via site key:

{
  "some-site-1": {
    "src": "some-site-1/src",
    "dst": "some-site-1/dist",
    "title": "Title Site 1",
    "url": "example-1.com",
    "name": "Example Site 1",
    "option-1": false,
    "options-2": [
      "foo",
      "bar",
      "baz"
    ]
  },
  "some-site-2": {
    "src": "some-site-2/src",
    "dst": "some-site-2/dist",
    "title": "Title Site 2",
    "url": "example-2.com",
    "name": "Example Site 2",
    "option-1": true,
  }
}

Above is a soyweb manifest that defines 2 sites: Example Site 1 and Example Site 2. Example Site 1 is accessed via some-site-1 site key.

Each site object contains options for the site, like the soyweb index generator and soyweb minifiers.

Real world example would be manifest.json.

A soyweb site can be thought of as the smallest unit of a website, and how your site will be organized into soyweb sites are entirely up to you.

In reality, a soyweb site only exists so that we can apply different soyweb options against different source roots. Multiple such sites may in reality make up 1 website.

soyweb ssg-go options

soyweb extends ssg-go options using ssg.Option type.

soyweb minifiers

soyweb provides webformat minifiers opitions for ssg, implemented as hooks that map 1 input data to 1 output data.

The minifiers is available to all programs under soyweb.

soyweb index generator

soyweb provides an automatic index generator, implemented as a ssg.Pipeline. This pipeline will automatically generate index sibling Markdowns, HTMLs, and directories.

The pipeline looks for marker file _index.soyweb somewhere under ${src}, and, if found, lists all links to the children (i.e. "articles").

The marker _index.soyweb can be empty, or contain template. If not empty, the template inside the marker will be treated as Markdown.

To be considered an entry by the generator, the marker's sibling has to satisfy at least one of the criteria:

  • A file with .md or .html extension

    The generated index will point to HTML extensions

  • A directory with index.html or index.md

    The generated index will point to HTML extensions

  • A directory with another marker _index.soyweb (recursive)

    The generated index will point to ${sibling}/index.html

The generator is currently available to soyweb via the site manifest specification.

Index generator: templates in markers

The generator allows markers to contain partial template.

The marker _index.soyweb could be a Markdown, and apart from having its content appended by the generated index, the file is handled normally like with other ssg-go input files.

If the marker _index.soyweb is empty, a default content header will be written. If the marker has some template, then the index list will be appended to the template in the output.

Index generator: how it's generated

The generated indexes are treated just like any other source Markdown files.

In other words, _header.html and _footer.html will surround the index generated from marker files. ssg.TitleFrom tags are respected and title extraction for the generated index is handled in the familiar fashion.

Only Markdown and HTML siblings are to be linked. For example, if we have 2 files 1.md and 2.html, then links will be generated for 1.html and 2.html.

Link titles are extracted from tag :ssg-title first (:ssg-title FooTitle), and if there's no such title, then the generator falls back to Markdown h1 titles (# FooTitle) will be picked as the child title within the index.

If a marker's sibling is a directory with index.md, then the titles will be extracted from the Markdown index like with files.

If there's no index.md, then the directory names will be used as titles. And instead of linking to /some/path/targetdir/index.md, the directory links ends with a slash, so /some/path/targetdir/ is the hyperlink generated.

Index generator: practical examples

Consider a ssg source directory src:

src
│
├── _footer.html
├── _header.html
├── _index.soyweb
│
├── foo.md
├── bar.md
│
├── 2022
│   ├── _index.soyweb
│   ├── lol.md
│   └── not_article.svg
│
├── 2023
│   ├── hahaha
│   │   └── index.md
│   ├── _index.soyweb
│   └── baz.md
│
└── somedir
    └── index.md

We see 3 markers, in src, src/2022, and src/2023. This means that we should get 3 generated index.htmls. But we also see that src/2022/index.html already exists.

Because ssg-go gives preferences to HTML files with matching base names, and because the generator respects this behavior, the source HTML will be copied and new index not generated.

If we focus on the root marker. We see that it has 5 siblings, namely, foo.md, bar.md, 2022, 2023, and somedir.

The generated index should have links to these 3 children. If the destination is dst, then the generated index from this root marker will be at dst/index.html, mirroring src/_index.soyweb location in src.

We can look up the special ssg files so that we can compare the output:

src/_header.html:

<!-- My blog header! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{from-tag}}</title>
</head>
<body>

src/_footer.html:

<!-- My blog footer! -->
</body>
</html>

src/_index.soyweb:

:ssg-title My blog!

# Welcome to my blog!

Below is the list of my articles!

src/foo.md:

# Foo article

Foo is better than fu

src/bar.md:

:ssg-title Bar article

# Barbarbarbar

Greeks called other peoples babarians because all they hear is barbarbar

src/somedir/index.md:

:ssg-title SomeDir title

# Welcome to SomeDir!

This is some directory

Now, the generated index dst/index.html looks like this:

<!-- My blog header! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My blog!</title>
</head>
<body>
<h1>Welcome to my blog!</h1>
<p>Below is the list of my articles!</p>
<ul>
  <li><p><a href="/2023/">2023</a></p></li>
  <li><p><a href="/2022/">2022</a></p></li>
  <li><p><a href="/bar.html">Bar article</a></p></li>
  <li><p><a href="/foo.html">Foo article</a></p></li>
  <li><p><a href="/somedir/">SomeDir title</a></p></li>
</ul>
<!-- My blog footer! -->
</body>
</html>

What about src/2023/_index.soyweb marker? If it's not empty, then the output will be generated in a fashion similar to the root marker example above.

But what if the marker src/2023/_index.soyweb is empty? If so, then the default heading will be used, and the generated dst/2023/index.html looks something like this:

<!-- My blog header! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SomeDefaultSsgGoTitle</title>
</head>
<body>
<h1>Index of somedir</h1>
<ul>
  <li><p><a href="/hahaha/">BarTitle</a></p></li>
  <li><p><a href="/baz.html">BazTitle</a></p></li>
</ul>
<!-- My blog footer! -->
</body>
</html>

Documentation

Index

Constants

View Source
const (
	MediaTypeHtml = "text/html"
	MediaTypeCss  = "style/css"
	MediaTypeJs   = "text/javascript"
	MediaTypeJson = "application/json"

	ExtHtml = ".html"
	ExtCss  = ".css"
	ExtJs   = ".js"
	ExtJson = ".json"
)
View Source
const (
	MarkerIndex string = "_index.soyweb"

	IndexGeneratorModeDefault IndexGeneratorMode = ""
	IndexGeneratorModeReverse IndexGeneratorMode = "reverse"
	IndexGeneratorModeModTime IndexGeneratorMode = "modtime"
)

Variables

View Source
var ErrWebFormatNotSupported = errors.New("unsupported web format")

Functions

func ApplyManifestV2

func ApplyManifestV2(m Manifest, f FlagsV2, do Stage) error

func ExtToFn

func ExtToFn(ext string) (func([]byte) ([]byte, error), error)

func ExtToMediaType

func ExtToMediaType(ext string) (string, error)

func HookMinify

func HookMinify(m map[string]MinifyFn) ssg.Hook

func HookMinifyDefault

func HookMinifyDefault(mediaTypes ssg.Set) ssg.Hook

func HookReplacer

func HookReplacer(r Replaces) ssg.Hook

func IndexGenerator

func IndexGenerator(s *ssg.Ssg) ssg.Pipeline

IndexGenerator returns an ssg.Pipeline that would look for marker file "_index.soyweb" within a directory.

Once it finds a marked directory, it inspects the children and generate a Markdown list with name index.md, which is later sent to supplied impl

func IndexGeneratorModTime

func IndexGeneratorModTime(s *ssg.Ssg) ssg.Pipeline

IndexGeneratorModTime returns an index generator that sort index entries by ModTime returned by fs.FileInfo

func IndexGeneratorReverse

func IndexGeneratorReverse(s *ssg.Ssg) ssg.Pipeline

IndexGeneratorReverse returns an index generator whose index list is populated reversed, i.e. descending alphanumerical sort

func IndexGeneratorTemplate

func IndexGeneratorTemplate(
	fnEntries func(entries []fs.FileInfo) []fs.FileInfo,
	fnGenIndex func(
		ssgSrc string,
		ignore func(path string) bool,
		parent string,
		siblings []fs.FileInfo,
		template []byte,
	) (
		string,
		error,
	),
) func(*ssg.Ssg) ssg.Pipeline

IndexGeneratorTemplate allows us to build an index generator pipeline from 2 components: 1. fnEntries - a function that intercepts entries and returns the actual entries to be used. This is useful when you want to implement some kind of entry filter, or just want to inspect entries before handling them over to the generators.

2. fnGenIndex - a function that is called for each marker _index.soyweb.

func MinifyAll

func MinifyAll(path string, data []byte) ([]byte, error)

func MinifyCss

func MinifyCss(og []byte) ([]byte, error)

func MinifyFile

func MinifyFile(path string) ([]byte, error)

func MinifyHtml

func MinifyHtml(og []byte) ([]byte, error)

func MinifyJs

func MinifyJs(og []byte) ([]byte, error)

func MinifyJson

func MinifyJson(og []byte) ([]byte, error)

func NewIndexGenerator

func NewIndexGenerator(m IndexGeneratorMode) func(*ssg.Ssg) ssg.Pipeline

Types

type CopyTarget

type CopyTarget struct {
	Target string `json:"-"`
	Force  bool   `json:"-"`
}

func (CopyTarget) String

func (t CopyTarget) String() string

type CopyTargets

type CopyTargets []CopyTarget

func (*CopyTargets) UnmarshalJSON

func (c *CopyTargets) UnmarshalJSON(b []byte) error

type FlagsNoMinify

type FlagsNoMinify struct {
	NoMinifyHtmlGenerate bool `arg:"--no-min-html,env:NO_MIN_HTML" help:"Do not minify converted HTML outputs"`
	NoMinifyHtmlCopy     bool `arg:"--no-min-html-copy,env:NO_MIN_HTML_COPY" help:"Do not minify all copied HTML"`
	NoMinifyCss          bool `arg:"--no-min-css,env:NO_MIN_CSS" help:"Do not minify CSS files"`
	NoMinifyJs           bool `arg:"--no-min-js,env:NO_MIN_JS" help:"Do not minify Javascript files"`
	NoMinifyJson         bool `arg:"--no-min-json,env:NO_MIN_JSON" help:"Do not minify JSON files"`
}

func (FlagsNoMinify) Flags

func (f FlagsNoMinify) Flags() FlagsV2

func (FlagsNoMinify) Skip

func (f FlagsNoMinify) Skip(ext string) bool

type FlagsV2

type FlagsV2 struct {
	NoCleanup       bool `arg:"--no-cleanup" help:"Skip cleanup stage"`
	NoCopy          bool `arg:"--no-copy" help:"Skip scopy stage"`
	NoBuild         bool `arg:"--no-build" help:"Skip build stage"`
	NoReplace       bool `arg:"--no-replace" help:"Do not do text replacements defined in manifest"`
	NoGenerateIndex bool `arg:"--no-gen-index" help:"Do not generate indexes on _index.soyweb"`

	MinifyHtmlGenerate bool `arg:"--min-html" help:"Minify converted HTML outputs"`
	MinifyHtmlCopy     bool `arg:"--min-html-copy" help:"Minify all copied HTML"`
	MinifyCss          bool `arg:"--min-css" help:"Minify CSS files"`
	MinifyJs           bool `arg:"--min-js" help:"Minify Javascript files"`
	MinifyJson         bool `arg:"--min-json" help:"Minify JSON files"`
}

FlagsV2 represents CLI arguments that could modify soyweb behavior, such as skipping stages and minifying content of certain file extensions.

func (FlagsV2) Hooks

func (f FlagsV2) Hooks() []ssg.Hook

func (FlagsV2) Stage

func (f FlagsV2) Stage() Stage

type IndexGeneratorMode

type IndexGeneratorMode string

type Manifest

type Manifest map[string]Site

func NewManifest

func NewManifest(filename string) (Manifest, error)

type MinifyFn

type MinifyFn func(data []byte) ([]byte, error)

type ReplaceTarget

type ReplaceTarget struct {
	Text  string `json:"-"`
	Count uint   `json:"-"` // 0 replaces all, 1 replaces once, 2 replaces twice, and so on
}

type Replaces

type Replaces map[string]ReplaceTarget

func (*Replaces) UnmarshalJSON

func (r *Replaces) UnmarshalJSON(b []byte) error

type Site

type Site struct {
	CleanUp           bool                   `json:"-"` // Remove files in Copies before copying them
	Copies            map[string]CopyTargets `json:"-"`
	GenerateIndex     bool                   `json:"-"`
	GenerateIndexMode IndexGeneratorMode     `json:"-"`
	Replaces          Replaces               `json:"-"`
	// contains filtered or unexported fields
}

func (*Site) Copy

func (s *Site) Copy() error

func (*Site) Dst

func (s *Site) Dst() string

func (*Site) Src

func (s *Site) Src() string

func (*Site) UnmarshalJSON

func (s *Site) UnmarshalJSON(b []byte) error

type Stage

type Stage int
const (
	StageCollect Stage = -1

	// These stages can be skipped
	StageCleanUp Stage = 1 << iota
	StageCopy
	StageBuild

	StageAll Stage = StageCollect | StageCleanUp | StageCopy | StageBuild
)

func (*Stage) Ok

func (s *Stage) Ok(targets ...Stage) bool

func (*Stage) Skip

func (s *Stage) Skip(targets ...Stage)

func (Stage) String

func (s Stage) String() string

Directories

Path Synopsis
cmd
minifier command
soyweb command
ssg-minifier command
debug

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL