diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-09 20:44:58 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-09 20:44:58 +0300 |
| commit | 3e61d09873065f5342efc414ee3ea0d5fdc4c767 (patch) | |
| tree | 7d0ac51cfb41b4774db6292deeb0cc3dce93cf07 /internal/post | |
| parent | 51f95f88ca78471a50b3fc62dbcea8edb609dc80 (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'internal/post')
| -rw-r--r-- | internal/post/post.go | 84 |
1 files changed, 84 insertions, 0 deletions
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/<ID>/. +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) +} |
