From 539a943576ecba25cd0f0ac5a5a148dbea589d1a Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 29 Apr 2021 13:39:52 +0000 Subject: [PATCH] start implementing metadata parsing support Signed-off-by: Christine Dodrill --- gempub.go | 105 ++++++++++++++++++++++++++++ gempub_test.go | 54 ++++++++++++++ testdata/metadata/bad/blank.txt | 0 testdata/metadata/good/complete.txt | 14 ++++ testdata/metadata/good/simple.txt | 4 ++ 5 files changed, 177 insertions(+) create mode 100644 gempub_test.go create mode 100644 testdata/metadata/bad/blank.txt create mode 100644 testdata/metadata/good/complete.txt create mode 100644 testdata/metadata/good/simple.txt diff --git a/gempub.go b/gempub.go index 2a6c83e..a7d6cd5 100644 --- a/gempub.go +++ b/gempub.go @@ -1 +1,106 @@ +// Package gempub implements the gempub 1.0.0 specification as described here: https://codeberg.org/oppenlab/gempub package gempub + +import ( + "bufio" + "errors" + "fmt" + "io" + "strings" + "time" +) + +type Metadata struct { + Title string // mandatory title of the work + GpubVersion string // mandatory Gempub format version: 1.0.0 + Index string // path to index.gmi, if none is specified it should be assumed to be in the root + Author string // author of the work + Language string // BCP 47 language code + Charset string // if not set, assume UTF-8 + Description string // human-readable description + Published time.Time // YYYY when date is unknown + PublishDate time.Time // YYYY-MM-DD eg. 2006-01-02 + RevisionDate time.Time // YYYY-MM-DD eg. 2006-01-02 + Copyright string // copyright of the book + License string // license of the book Version string a // human readable only, not meant to be parsed + Cover string // a JPG or PNG image which can be anywhere in the directory structure +} + +var ( + ErrNoTitle = errors.New("gempub: no title in document") +) + +func loadDateOrYear(when string) (t time.Time, err error) { + if len(when) == 4 { + t, err = time.Parse("2006", when) + return + } + + t, err = time.Parse("2006-01-02", when) + return +} + +func (m Metadata) Valid() error { + switch { + case m.Title == "": + return ErrNoTitle + case m.GpubVersion != "1.0.0": + return fmt.Errorf("gempub: wrong gempub version: %v", m.GpubVersion) + } + + return nil +} + +func ReadMetadata(r io.Reader) (*Metadata, error) { + var result Metadata + + sc := bufio.NewScanner(r) + for sc.Scan() { + line := sc.Text() + sp := strings.SplitN(line, ":", 2) + key, val := sp[0], sp[1] + val = strings.TrimSpace(val) + switch key { + case "title": + result.Title = val + case "gpubVersion": + result.GpubVersion = val + case "index": + result.Index = val + case "author": + result.Author = val + case "language": + result.Language = val + case "charset": + result.Charset = val + case "description": + result.Description = val + case "published": + when, err := loadDateOrYear(val) + if err != nil { + return nil, fmt.Errorf("gempub: can't understand date %q: %v", val, err) + } + result.Published = when + case "publishDate": + when, err := loadDateOrYear(val) + if err != nil { + return nil, fmt.Errorf("gempub: can't understand date %q: %v", val, err) + } + result.PublishDate = when + case "revisionDate": + when, err := loadDateOrYear(val) + if err != nil { + return nil, fmt.Errorf("gempub: can't understand date %q: %v", val, err) + } + result.RevisionDate = when + case "copyright": + result.Copyright = val + case "license": + result.License = val + case "cover": + result.Cover = val + } + } + + return &result, result.Valid() +} diff --git a/gempub_test.go b/gempub_test.go new file mode 100644 index 0000000..60b6045 --- /dev/null +++ b/gempub_test.go @@ -0,0 +1,54 @@ +package gempub + +import ( + "embed" + "io/fs" + "testing" +) + +//go:embed testdata/* +var testdata embed.FS + +func TestGoodMetadata(t *testing.T) { + fnames, err := fs.Glob(testdata, "testdata/metadata/good/*") + if err != nil { + t.Fatal(err) + } + + for _, fname := range fnames { + t.Run(fname, func(t *testing.T) { + fin, err := testdata.Open(fname) + if err != nil { + t.Fatal(err) + } + defer fin.Close() + + md, err := ReadMetadata(fin) + if err != nil { + t.Fatal(err) + } + }) + } +} + +func TestBadMetadata(t *testing.T) { + fnames, err := fs.Glob(testdata, "testdata/metadata/bad/*") + if err != nil { + t.Fatal(err) + } + + for _, fname := range fnames { + t.Run(fname, func(t *testing.T) { + fin, err := testdata.Open(fname) + if err != nil { + t.Fatal(err) + } + defer fin.Close() + + _, err = ReadMetadata(fin) + if err == nil { + t.Fatal("wanted error, but got none") + } + }) + } +} diff --git a/testdata/metadata/bad/blank.txt b/testdata/metadata/bad/blank.txt new file mode 100644 index 0000000..e69de29 diff --git a/testdata/metadata/good/complete.txt b/testdata/metadata/good/complete.txt new file mode 100644 index 0000000..6c0a206 --- /dev/null +++ b/testdata/metadata/good/complete.txt @@ -0,0 +1,14 @@ +title: Spellblade +gpubVersion: 1.0.0 +index: book/index.gmi +author: Xe +language: en-US +charset: UTF-8 +description: A woman realizes the gap between two opposites and their place in the middle. +published: 2021 +publishDate: 2021-12-31 +revisionDate: 2022-01-01 +copyright: Christine Dodrill +license: All Rights Reserved +version: 1.0.0 +cover: cover.jpg diff --git a/testdata/metadata/good/simple.txt b/testdata/metadata/good/simple.txt new file mode 100644 index 0000000..2624311 --- /dev/null +++ b/testdata/metadata/good/simple.txt @@ -0,0 +1,4 @@ +title: Star Maker +author: Olaf Stapledon +index: ./capsule/index.gmi +gpubVersion: 1.0.0