From 3e61d09873065f5342efc414ee3ea0d5fdc4c767 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 9 Apr 2026 20:44:58 +0300 Subject: add snonux static microblog generator Full Go implementation with: - txt/md/image/audio input processing, URL auto-linking in .txt files - Paginated HTML output with Atom feed - 11 visual themes: neon, terminal, synthwave, minimal, brutalist, paper, aurora, matrix, ocean, retro, glass (selectable via --theme flag) - Keyboard navigation (j/k/arrows, Enter modal, h/l page nav) - Shared nav templates (navhints, navmodal, navscript) across all themes - Magefile build automation; integration test suite covering all themes Co-Authored-By: Claude Sonnet 4.6 --- internal/post/post.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 internal/post/post.go (limited to 'internal/post') diff --git a/internal/post/post.go b/internal/post/post.go new file mode 100644 index 0000000..cdef546 --- /dev/null +++ b/internal/post/post.go @@ -0,0 +1,84 @@ +// Package post defines the Post data model and its JSON serialisation format. +// Each post is stored as post.json inside its own directory under outdir/posts/. +// This allows pages to be re-generated without re-processing the original inputs. +package post + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" +) + +// Type enumerates the supported input content types. +type Type string + +const ( + TypeText Type = "text" + TypeMarkdown Type = "markdown" + TypeImage Type = "image" + TypeAudio Type = "audio" +) + +// Post represents a single microblog entry. +// It is persisted as post.json in outdir/posts//. +type Post struct { + // ID is the unique directory-name-safe timestamp, e.g. "2026-04-09-143022". + ID string `json:"id"` + + // Timestamp is the moment the post was processed (UTC). + Timestamp time.Time `json:"timestamp"` + + // PostType determines how the content was generated and how it should be rendered. + PostType Type `json:"type"` + + // Content is the pre-rendered HTML snippet for this post (without outer post-card wrapper). + Content string `json:"content"` + + // Assets lists filenames (not paths) of any asset files stored alongside post.json. + Assets []string `json:"assets,omitempty"` +} + +// Save writes the post as post.json into dir. +func (p *Post) Save(dir string) error { + data, err := json.MarshalIndent(p, "", " ") + if err != nil { + return fmt.Errorf("marshal post %s: %w", p.ID, err) + } + + path := filepath.Join(dir, "post.json") + if err := os.WriteFile(path, data, 0o644); err != nil { + return fmt.Errorf("write post.json for %s: %w", p.ID, err) + } + + return nil +} + +// Load reads and parses post.json from dir. +func Load(dir string) (*Post, error) { + path := filepath.Join(dir, "post.json") + + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read post.json in %s: %w", dir, err) + } + + var p Post + if err := json.Unmarshal(data, &p); err != nil { + return nil, fmt.Errorf("unmarshal post.json in %s: %w", dir, err) + } + + return &p, nil +} + +// NewID generates a unique post ID from the given time. +// Format: YYYY-MM-DD-HHmmss, optionally suffixed with -N for collisions. +func NewID(t time.Time, suffix int) string { + base := t.UTC().Format("2006-01-02-150405") + if suffix == 0 { + return base + } + + return fmt.Sprintf("%s-%d", base, suffix) +} -- cgit v1.2.3