fragmentsgo

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 17 Imported by: 0

README

fragmentsgo

A framework-agnostic Markdown content engine for Go — the counterpart of Kotlin's fragments4k. Point it at a directory of Markdown files with YAML front matter and get a blog, static pages, search, RSS, sitemaps, reading time, author profiles, and SEO metadata. Rendering and routing stay yours; everything else is here.

Quick start

blogRepo := fragmentsgo.NewFileSystemRepository(fragmentsgo.RepositoryOptions{
    Path: "./content/blog",
    URLBuilder: func(f *fragmentsgo.Fragment) string {
        return "/blog/" + f.Date.Format("2006/01") + "/" + f.Slug
    },
})
pageRepo := fragmentsgo.NewFileSystemRepository(fragmentsgo.RepositoryOptions{
    Path:    "./content/pages",
    Ordered: true,
})
_ = blogRepo.Load()
_ = pageRepo.Load()

adapter := httpadapter.New(httpadapter.Config{
    Blog:      blog.New(blogRepo, 10),
    Static:    static.New(pageRepo),
    Search:    search.New(blogRepo, pageRepo),
    Renderer:  renderer, // your templates: blog, post, archive, search, static
    SiteTitle: "My Site",
    SiteURL:   "https://example.com",
})
mux := http.NewServeMux()
adapter.Mount(mux) // /blog, /blog/tag/{tag}, /blog/archive, /rss.xml, /sitemap.xml, /search

See cmd/example for a complete runnable site.

Packages

Package Purpose
fragmentsgo Fragment model with typed front-matter accessors, lifecycle statuses, Markdown parser with sanitizer profiles, file-system repository (load/reload, status rewrites), author repository, reading time
blog Paginated overviews, tag listings, year/month archives, previous/next
static Ordered pages and custom sections (projects, articles)
search Scored in-memory search (Search, Autocomplete, SearchByTag) with phrase and fuzzy options — the pragmatic stand-in for Lucene
seo Open Graph/Twitter tags, canonical URLs, robots directives, JSON-LD (Organization, WebSite, BlogPosting, Breadcrumb)
rss RSS 2.0 feed builder
sitemap sitemap.xml builder
httpadapter Mounts everything on any *http.ServeMux
reload Dependency-free development watcher: polls directories for Markdown changes and fires a debounced callback
imageopt Re-encodes content images for the web: JPEGs downscaled and re-compressed, PNGs downscaled losslessly, other formats passed through
cmd/fragmentsgo CLI: scaffold fragments (new) and validate directories (validate)

Development workflow

# Scaffold a draft (slug from the title; refuses to overwrite)
go run ./cmd/fragmentsgo new -dir ./content/blog "My Next Post"

# Validate front matter, slug collisions, and URL clashes
go run ./cmd/fragmentsgo validate ./content/blog ./content/articles

# Optimize a content image (in place or to a new path)
go run ./cmd/fragmentsgo optimize -max 1600 -quality 80 photo.jpg

# Hot-reload content while developing (poll-based, stdlib only)
watcher := reload.Watch(ctx, []string{"content"}, reload.Options{Interval: 2 * time.Second}, func() {
    _ = store.Refresh() // or repo.Load()
})

Content model

---
title: Hello World
slug: hello            # optional, derived from title
date: 2026-03-01
tags: [go, testing]
author: jane           # matches an author profile
image: /images/hero.png
preview: Short summary # optional, first paragraph by default
template: wide         # optional alternate template
order: 3               # ordering for pages/sections
status: draft          # draft|review|approved|published|archived
publishAt: 2026-06-01T09:00:00Z  # future date → scheduled
expiresAt: 2027-01-01  # past date → expired
githubRepo: owner/repo # any custom field, read via GetString/GetInt/...
---
Body in **Markdown**.

Fragments are visible only when they resolve to published (explicitly, or by default when no restrictive fields are set). The repository rewrites front matter for lifecycle moves:

_ = repo.UpdateStatus("hello", fragmentsgo.StatusPublished)
_ = repo.Schedule("hello", time.Date(2026, 12, 1, 9, 0, 0, 0, time.UTC))
_ = repo.Archive("hello")

Sanitizer profiles

SanitizerRelaxedTrustedAuthor (default) keeps rich formatting while stripping scripts and event handlers — for content you author. SanitizerStrict reduces to basic inline formatting — for untrusted contributions.

Status of this port

Covers the fragments4k core: content repository, blog/static engines, search, SEO, RSS, sitemap, authors, and an net/http adapter (Go needs only one). Not ported: Lucene-backed index (replaced by the scored in-memory engine), CLI scaffolding, live reload, chat/social modules, image optimization, and per-framework adapters.

License

MIT — see LICENSE.

Documentation

Overview

Package fragmentsgo is a framework-agnostic Markdown content engine for Go, modeled on fragments4k: it turns a directory of Markdown files with YAML front matter into blog posts, static pages, and searchable content — with reading time, author profiles, lifecycle statuses, and SEO metadata.

The core type is Fragment; content lives behind a Repository (see NewFileSystemRepository); the blog, static, search, seo, rss, sitemap, and httpadapter subpackages build higher-level engines on top.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("fragmentsgo: fragment not found")

ErrNotFound is returned when no fragment matches a lookup.

Functions

func FirstParagraph

func FirstParagraph(body string) string

FirstParagraph extracts the first prose paragraph of a Markdown body as preview text: headings, lists, code fences, tables, and quotes are skipped.

func PlainText

func PlainText(html string) string

PlainText strips HTML tags and collapses whitespace.

func ReadingTimeOf

func ReadingTimeOf(text string) int

ReadingTimeOf estimates reading minutes for a plain-text body at 200 words per minute, minimum one for non-empty text.

func Slugify

func Slugify(value string) string

Slugify converts a title into a URL slug.

func SortDated

func SortDated(fragments []*Fragment)

SortDated orders dated fragments newest-first, title as tiebreaker.

func SortOrdered

func SortOrdered(fragments []*Fragment)

SortOrdered orders fragments by Order, then Title (pages, projects).

Types

type Author

type Author struct {
	ID          string            `yaml:"id"`
	Name        string            `yaml:"name"`
	Slug        string            `yaml:"slug"`
	Bio         string            `yaml:"bio"`
	GitHub      string            `yaml:"github"`
	Twitter     string            `yaml:"twitter"`
	SocialLinks map[string]string `yaml:"socialLinks"`
}

Author is a content author profile loaded from a .author.yml file.

func (*Author) SocialEntries

func (a *Author) SocialEntries() []AuthorLink

SocialEntries returns the author's social links with GitHub/Twitter expanded to full URLs, in a stable order.

type AuthorLink struct {
	Name string
	URL  string
}

AuthorLink is one outbound social profile link.

type AuthorRepository

type AuthorRepository struct {
	// contains filtered or unexported fields
}

AuthorRepository loads author profiles from a directory of .author.yml files and resolves them by id, slug, or display name.

func NewAuthorRepository

func NewAuthorRepository(root string) (*AuthorRepository, error)

NewAuthorRepository loads every *.author.yml file under root. A missing directory yields an empty repository.

func (*AuthorRepository) All

func (r *AuthorRepository) All() []*Author

All returns every author profile ordered by name.

func (*AuthorRepository) Resolve

func (r *AuthorRepository) Resolve(name string) *Author

Resolve finds an author by id, slug, or display name; nil when the name is empty or unknown.

type Fragment

type Fragment struct {
	// Slug is the URL identifier, from front matter or derived from Title.
	Slug string
	// Title is the document heading.
	Title string
	// Date is the publication date from front matter (zero when absent).
	Date time.Time
	// Updated is the optional last-modified date from the "updated"
	// front-matter key (zero when absent); it never affects visibility
	// or ordering, only feeds/sitemap metadata.
	Updated time.Time
	// Tags and Categories come from front matter.
	Tags       []string
	Categories []string
	// Author names an author profile (see AuthorRepository).
	Author string
	// Image is a front-matter hero/cover image path.
	Image string
	// Preview is the summary text: the front-matter preview when present,
	// otherwise the first paragraph of the body.
	Preview string
	// Template names an alternate rendering template.
	Template string
	// Order positions fragments in ordered listings (projects, pages).
	Order int
	// Status is the effective lifecycle status after resolution.
	Status Status
	// URL is the computed public URL (see RepositoryOptions.URLBuilder).
	URL string

	// HTML is the sanitized rendered Markdown.
	HTML template.HTML
	// BodyText is the tag-stripped plain text of the body (search input).
	BodyText string
	// ReadingTime is the estimated reading time in minutes (200 wpm).
	ReadingTime int

	// Fields holds the raw front matter for typed access to custom keys.
	Fields map[string]any
	// SourcePath is the file the fragment was loaded from.
	SourcePath string
}

Fragment is one parsed Markdown document.

func (*Fragment) DateValue

func (f *Fragment) DateValue() *time.Time

DateValue returns the date as a pointer, nil when unset — convenient for templates that format optional dates.

func (*Fragment) GetBool

func (f *Fragment) GetBool(key string) bool

GetBool reads a custom front-matter field as a boolean.

func (*Fragment) GetInt

func (f *Fragment) GetInt(key string) int

GetInt reads a custom front-matter field as an int.

func (*Fragment) GetString

func (f *Fragment) GetString(key string) string

GetString reads a custom front-matter field as a string.

func (*Fragment) GetStringList

func (f *Fragment) GetStringList(key string) []string

GetStringList reads a custom front-matter field as a string slice.

func (*Fragment) HasTag

func (f *Fragment) HasTag(tag string) bool

HasTag reports whether the fragment carries a tag, case-insensitively.

func (*Fragment) Visible

func (f *Fragment) Visible() bool

Visible reports whether the fragment should appear publicly: published, publish date reached, and not expired or archived.

type MarkdownParser

type MarkdownParser struct {
	// contains filtered or unexported fields
}

MarkdownParser converts Markdown bodies into sanitized HTML.

func NewMarkdownParser

func NewMarkdownParser(profile SanitizerProfile) *MarkdownParser

NewMarkdownParser builds a parser with the given sanitizer profile.

func (*MarkdownParser) Render

func (p *MarkdownParser) Render(source []byte) (string, error)

Render converts Markdown to sanitized HTML.

type Repository

type Repository interface {
	// Load (re)reads every fragment from disk.
	Load() error
	// All returns fragments in listing order (newest first for dated
	// content; Order then Title for undated content), visible ones only
	// unless IncludeInvisible was set.
	All() []*Fragment
	// Everything returns every fragment — including drafts, scheduled,
	// and other invisible states — in the same listing order. It is the
	// administrative lifecycle view; public listings should use All.
	Everything() []*Fragment
	// BySlug finds a fragment by slug.
	BySlug(slug string) (*Fragment, error)
	// ByURL finds a fragment by its public URL.
	ByURL(url string) (*Fragment, error)
	// UpdateStatus rewrites a fragment's status front matter and reloads it.
	UpdateStatus(slug string, status Status) error
	// Schedule sets a future publish date and reloads the fragment.
	Schedule(slug string, at time.Time) error
	// Archive marks a fragment archived and reloads it.
	Archive(slug string) error
}

Repository provides access to a set of fragments.

func NewFileSystemRepository

func NewFileSystemRepository(options RepositoryOptions) Repository

NewFileSystemRepository creates a repository over a directory of Markdown files. Call Load before first use.

type RepositoryOptions

type RepositoryOptions struct {
	// Path is the content directory holding .md files.
	Path string
	// BaseURL prefixes generated URLs (e.g. "/projects"). Mutually
	// exclusive with URLBuilder.
	BaseURL string
	// URLBuilder computes a fragment's public URL; when nil the URL is
	// BaseURL + "/" + slug (or "/"+slug without a base).
	URLBuilder func(f *Fragment) string
	// Parser renders Markdown; nil selects the relaxed trusted-author
	// profile.
	Parser *MarkdownParser
	// Now overrides time for status resolution (tests).
	Now func() time.Time
	// IncludeInvisible keeps drafts and scheduled fragments in listings so
	// preview surfaces can show them; they are excluded by default.
	IncludeInvisible bool
	// Ordered selects listing order: true sorts by Order then Title
	// (pages, projects); false (default) sorts dated content newest first.
	Ordered bool
	// Exclude drops files whose base name matches (checked case-blind
	// against the full path), e.g. to keep self-referential entries out.
	Exclude func(path string) bool
}

RepositoryOptions configures a [FileSystemRepository].

type SanitizerProfile

type SanitizerProfile int

SanitizerProfile selects the HTML policy applied to rendered Markdown.

const (
	// SanitizerRelaxedTrustedAuthor allows standard rich content (images,
	// links, headings, code, tables) — the default, matching fragments4k's
	// RELAXED_TRUSTED_AUTHOR profile for content you author yourself.
	SanitizerRelaxedTrustedAuthor SanitizerProfile = iota
	// SanitizerStrict strips everything down to basic formatting — for
	// content contributed by untrusted parties.
	SanitizerStrict
)

type Status

type Status string

Status is a fragment's lifecycle state. Content becomes publicly visible only when it resolves to Published (explicitly, or via a reached publish-date and no expiry).

const (
	StatusDraft     Status = "draft"
	StatusReview    Status = "review"
	StatusApproved  Status = "approved"
	StatusPublished Status = "published"
	StatusScheduled Status = "scheduled"
	StatusArchived  Status = "archived"
	StatusExpired   Status = "expired"
)

Directories

Path Synopsis
Package blog implements the blog engine over a dated fragment repository: paginated overviews, tag listings, archives, and previous/next navigation.
Package blog implements the blog engine over a dated fragment repository: paginated overviews, tag listings, archives, and previous/next navigation.
cmd
example command
Command example is a minimal fragmentsgo site: a blog, an about page, search, RSS, and a sitemap rendered through html/template.
Command example is a minimal fragmentsgo site: a blog, an about page, search, RSS, and a sitemap rendered through html/template.
fragmentsgo command
Command fragmentsgo scaffolds and validates content directories for fragmentsgo-based sites.
Command fragmentsgo scaffolds and validates content directories for fragmentsgo-based sites.
Package httpadapter mounts a fragmentsgo content site on any net/http mux: blog routes (paged listing, tags, dated posts, archive), search, static pages, and the rss.xml and sitemap.xml endpoints.
Package httpadapter mounts a fragmentsgo content site on any net/http mux: blog routes (paged listing, tags, dated posts, archive), search, static pages, and the rss.xml and sitemap.xml endpoints.
Package imageopt re-encodes content images to web-friendly sizes: JPEGs are downscaled and re-compressed, PNGs are downscaled losslessly, and anything else passes through untouched.
Package imageopt re-encodes content images to web-friendly sizes: JPEGs are downscaled and re-compressed, PNGs are downscaled losslessly, and anything else passes through untouched.
Package reload provides a dependency-free content watcher for development servers: it polls the watched directories and invokes a callback once changes settle.
Package reload provides a dependency-free content watcher for development servers: it polls the watched directories and invokes a callback once changes settle.
Package rss builds RSS 2.0 feeds from fragments.
Package rss builds RSS 2.0 feeds from fragments.
Package search provides an in-memory scored search engine over fragment repositories — the pragmatic Go stand-in for fragments4k's Lucene engine.
Package search provides an in-memory scored search engine over fragment repositories — the pragmatic Go stand-in for fragments4k's Lucene engine.
Package seo builds per-page SEO metadata: Open Graph and Twitter tags, canonical URLs, robots directives, and JSON-LD structured data — the Go counterpart of fragments4k's fragments-seo module.
Package seo builds per-page SEO metadata: Open Graph and Twitter tags, canonical URLs, robots directives, and JSON-LD structured data — the Go counterpart of fragments4k's fragments-seo module.
Package sitemap builds sitemap.xml documents from page URLs.
Package sitemap builds sitemap.xml documents from page URLs.
Package static implements the engine for undated, ordered content: standalone pages and custom sections like projects or articles.
Package static implements the engine for undated, ordered content: standalone pages and custom sections like projects or articles.

Jump to

Keyboard shortcuts

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