Diffstat (limited to 'internal/draft')
| -rw-r--r-- | internal/draft/parser.go | 179 | ||||
| -rw-r--r-- | internal/draft/parser_test.go | 83 |
2 files changed, 0 insertions, 262 deletions
diff --git a/internal/draft/parser.go b/internal/draft/parser.go deleted file mode 100644 index 2761c87..0000000 --- a/internal/draft/parser.go +++ /dev/null @@ -1,179 +0,0 @@ -package draft - -import ( - "regexp" - "strings" - - "github.com/SayaAndy/saya-today-article-metadata-add/internal/frontmatter" -) - -// layoutTokenRe matches gallery layout hints like "2x", "3x" that are -// meaningful only to the sayauz renderer and must be dropped elsewhere. -var layoutTokenRe = regexp.MustCompile(`^\d+x$`) - -type BlockKind int - -const ( - TextBlock BlockKind = iota - GalleryBlock -) - -// Block is one ordered piece of a section: either a run of markdown text or a -// gallery. Exactly one of Text / Gallery is meaningful depending on Kind. -type Block struct { - Kind BlockKind - Text string - Gallery *Gallery -} - -type Gallery struct { - Timezone string - KeyPrefix string - Photos []Photo -} - -type Photo struct { - Key string // KeyPrefix + filename, e.g. "Sevsk/20241008-093833.jpg" - Caption string -} - -// Section is the content between two "..." separator lines, split into ordered -// text and gallery blocks. -type Section struct { - Index int - Blocks []Block -} - -type Document struct { - SourcePath string - Codename string - Metadata *frontmatter.Metadata - RawContent []byte - Body string - Sections []Section -} - -// ParseDraft parses a draft file: frontmatter metadata, the markdown body, and -// the body split into sections (on "..." lines) of ordered text/gallery blocks. -func ParseDraft(sourcePath, codename string, content []byte) (*Document, error) { - metadata, body, err := frontmatter.ParseFrontmatter(content) - if err != nil { - return nil, err - } - - doc := &Document{ - SourcePath: sourcePath, - Codename: codename, - Metadata: metadata, - RawContent: content, - Body: string(body), - } - doc.Sections = splitSections(doc.Body) - - return doc, nil -} - -func splitSections(body string) []Section { - lines := strings.Split(body, "\n") - - var sections []Section - var chunk []string - - flush := func() { - blocks := parseBlocks(chunk) - chunk = nil - if len(blocks) == 0 { - return - } - sections = append(sections, Section{Index: len(sections), Blocks: blocks}) - } - - for _, line := range lines { - if strings.TrimSpace(line) == "..." { - flush() - continue - } - chunk = append(chunk, line) - } - flush() - - return sections -} - -func parseBlocks(lines []string) []Block { - var blocks []Block - var text []string - - flushText := func() { - joined := strings.TrimSpace(strings.Join(text, "\n")) - text = nil - if joined != "" { - blocks = append(blocks, Block{Kind: TextBlock, Text: joined}) - } - } - - for i := 0; i < len(lines); i++ { - trimmed := strings.TrimSpace(lines[i]) - if strings.HasPrefix(trimmed, "{Gallery:") { - flushText() - gallery, next := parseGallery(lines, i) - if gallery != nil { - blocks = append(blocks, Block{Kind: GalleryBlock, Gallery: gallery}) - } - i = next - continue - } - text = append(text, lines[i]) - } - flushText() - - return blocks -} - -// parseGallery reads a {Gallery:...} block starting at lines[start] and returns -// the parsed gallery plus the index of the closing {/Gallery} line (or the last -// consumed line if unterminated). -func parseGallery(lines []string, start int) (*Gallery, int) { - header := strings.TrimSpace(lines[start]) - header = strings.TrimPrefix(header, "{Gallery:") - header = strings.TrimSuffix(header, "}") - tz, keyPrefix, _ := strings.Cut(header, ":") - - gallery := &Gallery{Timezone: tz, KeyPrefix: keyPrefix} - - i := start + 1 - for ; i < len(lines); i++ { - trimmed := strings.TrimSpace(lines[i]) - if trimmed == "{/Gallery}" { - break - } - if trimmed == "" { - continue - } - gallery.Photos = append(gallery.Photos, parsePhoto(keyPrefix, trimmed)) - } - - return gallery, i -} - -func parsePhoto(keyPrefix, line string) Photo { - parts := strings.Split(line, "|") - for i := range parts { - parts[i] = strings.TrimSpace(parts[i]) - } - - filename := parts[0] - - var captionParts []string - for _, p := range parts[1:] { - if p == "" || layoutTokenRe.MatchString(p) { - continue - } - captionParts = append(captionParts, p) - } - - return Photo{ - Key: keyPrefix + filename, - Caption: strings.Join(captionParts, " "), - } -} diff --git a/internal/draft/parser_test.go b/internal/draft/parser_test.go deleted file mode 100644 index c6d454d..0000000 --- a/internal/draft/parser_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package draft - -import ( - "os" - "strings" - "testing" -) - -func TestParseDraftSevsk(t *testing.T) { - raw, err := os.ReadFile("../../.draft/sevsk.md.draft") - if err != nil { - t.Fatal(err) - } - - doc, err := ParseDraft("../../.draft/sevsk.md.draft", "sevsk", raw) - if err != nil { - t.Fatal(err) - } - if doc.Metadata == nil || doc.Metadata.Title == "" { - t.Fatal("expected frontmatter metadata") - } - - var galleries int - for _, section := range doc.Sections { - for _, block := range section.Blocks { - if block.Kind != GalleryBlock { - continue - } - galleries++ - for _, p := range block.Gallery.Photos { - if !strings.HasPrefix(p.Key, "Sevsk/20241008-") { - t.Errorf("unexpected photo key %q", p.Key) - } - if strings.Contains(p.Caption, "2x") { - t.Errorf("layout token leaked into caption %q", p.Caption) - } - } - } - } - if galleries == 0 { - t.Fatal("expected at least one gallery") - } - - // First gallery: 4 photos, captions on photos 1 and 3, none on 2; photo 4 had "2x |" => empty caption. - first := firstGallery(doc) - if first == nil || len(first.Photos) != 4 { - t.Fatalf("first gallery: want 4 photos, got %v", first) - } - if first.Timezone != "Europe/Moscow" { - t.Errorf("first gallery tz = %q", first.Timezone) - } - if first.Photos[0].Caption != "Никольская церковь. Действующая" { - t.Errorf("photo[0] caption = %q", first.Photos[0].Caption) - } - if first.Photos[1].Caption != "" { - t.Errorf("photo[1] caption = %q, want empty", first.Photos[1].Caption) - } - if first.Photos[3].Caption != "" { - t.Errorf("photo[3] caption = %q, want empty (only 2x token)", first.Photos[3].Caption) - } - if first.Photos[3].Key != "Sevsk/20241008-094036.jpg" { - t.Errorf("photo[3] key = %q", first.Photos[3].Key) - } -} - -func firstGallery(doc *Document) *Gallery { - for _, section := range doc.Sections { - for _, block := range section.Blocks { - if block.Kind == GalleryBlock { - return block.Gallery - } - } - } - return nil -} - -func TestSplitSectionsSeparator(t *testing.T) { - body := "intro\n...\nmiddle\n...\nend" - sections := splitSections(body) - if len(sections) != 3 { - t.Fatalf("want 3 sections, got %d", len(sections)) - } -} |