summaryrefslogtreecommitdiff
path: root/internal/processor/processor.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/processor/processor.go')
-rw-r--r--internal/processor/processor.go142
1 files changed, 37 insertions, 105 deletions
diff --git a/internal/processor/processor.go b/internal/processor/processor.go
index 0c03d86..bb3a84d 100644
--- a/internal/processor/processor.go
+++ b/internal/processor/processor.go
@@ -32,6 +32,31 @@ import (
"codeberg.org/snonux/snonux/internal/post"
)
+// PostBuilder is the abstraction used to validate and commit a single post type.
+// Each concrete builder handles one file extension (e.g. .txt, .png, .mp3).
+// Registering a new builder is enough to add support for a new type — no changes
+// to the core planning or commit loops are required.
+type PostBuilder interface {
+ // Plan validates the source file and returns everything needed to commit it later.
+ Plan(srcPath string, ext string) (postPlan, error)
+ // Commit performs the mutations for this post type and returns the populated Post,
+ // plus any extra inbox files that should be cleaned up after a successful save.
+ Commit(plan postPlan, postDir string, id string, now time.Time) (*post.Post, []string, error)
+}
+
+// builders maps a lower-case file extension to its PostBuilder.
+var builders = make(map[string]PostBuilder)
+
+// register adds a PostBuilder for the given extension. Panics on duplicates so
+// misconfiguration is caught at start-up.
+func register(ext string, b PostBuilder) {
+ ext = strings.ToLower(ext)
+ if _, exists := builders[ext]; exists {
+ panic(fmt.Sprintf("duplicate PostBuilder for extension %q", ext))
+ }
+ builders[ext] = b
+}
+
// Run scans cfg.InputDir and processes every eligible file into a post directory
// under cfg.OutputDir/posts/. It uses a two-phase commit pattern:
//
@@ -97,46 +122,22 @@ type postPlan struct {
mdHTML string
localImages []string
validatedImage image.Image
+ builder PostBuilder
}
// planPost validates a single source file and returns a plan containing
// everything needed to commit it later. It performs no mutations.
func planPost(srcPath string) (postPlan, error) {
ext := strings.ToLower(filepath.Ext(srcPath))
- plan := postPlan{srcPath: srcPath, ext: ext}
-
- switch ext {
- case ".txt":
- html, err := processTxt(srcPath)
- if err != nil {
- return postPlan{}, err
- }
- plan.textHTML = html
-
- case ".md":
- html, locals, err := processMd(srcPath)
- if err != nil {
- return postPlan{}, err
- }
- plan.mdHTML = html
- plan.localImages = locals
-
- case ".png", ".jpg", ".jpeg", ".gif":
- img, err := validateImage(srcPath)
- if err != nil {
- return postPlan{}, err
- }
- plan.validatedImage = img
-
- case ".mp3":
- if err := validateAudio(srcPath); err != nil {
- return postPlan{}, err
- }
-
- default:
+ b, ok := builders[ext]
+ if !ok {
return postPlan{}, fmt.Errorf("unsupported file type: %s", ext)
}
-
+ plan, err := b.Plan(srcPath, ext)
+ if err != nil {
+ return postPlan{}, err
+ }
+ plan.builder = b
return plan, nil
}
@@ -153,79 +154,10 @@ func commitPlan(plan postPlan, postsDir string, now time.Time) error {
return fmt.Errorf("create post dir %s: %w", id, err)
}
- var p *post.Post
- var inboxExtras []string
-
- switch plan.ext {
- case ".txt":
- p = &post.Post{
- ID: id,
- Timestamp: now,
- PostType: post.TypeText,
- Content: plan.textHTML,
- }
-
- case ".md":
- html := plan.mdHTML
- for _, name := range plan.localImages {
- html = strings.ReplaceAll(html,
- fmt.Sprintf(`src="%s"`, name),
- fmt.Sprintf(`src="posts/%s/%s"`, id, name))
- }
-
- sourceDir := filepath.Dir(plan.srcPath)
- for _, name := range plan.localImages {
- src := filepath.Join(sourceDir, name)
- dst := filepath.Join(postDir, name)
- if err := copyFile(src, dst); err != nil {
- _ = os.RemoveAll(postDir)
- return fmt.Errorf("copy markdown asset %s: %w", name, err)
- }
- inboxExtras = append(inboxExtras, src)
- }
-
- p = &post.Post{
- ID: id,
- Timestamp: now,
- PostType: post.TypeMarkdown,
- Content: html,
- Assets: plan.localImages,
- }
-
- case ".png", ".jpg", ".jpeg", ".gif":
- if err := writeImageAsset(plan.validatedImage, postDir); err != nil {
- _ = os.RemoveAll(postDir)
- return err
- }
- src := fmt.Sprintf("posts/%s/image.jpg", id)
- html := fmt.Sprintf(`<img src="%s" alt="" class="post-image">`, src)
- p = &post.Post{
- ID: id,
- Timestamp: now,
- PostType: post.TypeImage,
- Content: html,
- Assets: []string{"image.jpg"},
- }
-
- case ".mp3":
- outName := filepath.Base(plan.srcPath)
- dst := filepath.Join(postDir, outName)
- if err := copyFile(plan.srcPath, dst); err != nil {
- _ = os.RemoveAll(postDir)
- return err
- }
- src := fmt.Sprintf("posts/%s/%s", id, outName)
- html := fmt.Sprintf(
- `<audio controls class="post-audio"><source src="%s" type="audio/mpeg">Your browser does not support audio.</audio>`,
- src,
- )
- p = &post.Post{
- ID: id,
- Timestamp: now,
- PostType: post.TypeAudio,
- Content: html,
- Assets: []string{outName},
- }
+ p, inboxExtras, err := plan.builder.Commit(plan, postDir, id, now)
+ if err != nil {
+ _ = os.RemoveAll(postDir)
+ return err
}
if err := p.Save(postDir); err != nil {