From 3a8dfb2344017e32320181cb405b1b0d4ceb2328 Mon Sep 17 00:00:00 2001 From: "Thomas M. Edwards" Date: Wed, 10 Feb 2021 16:22:52 -0600 Subject: [PATCH] ADD: Add JSON output (`-j, --json`). --- config.go | 4 ++ passage.go | 92 +---------------------------------------- passagedata.go | 6 +++ passageout.go | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ storydata.go | 12 ++++++ storyout.go | 35 ++++++++++++++++ tweego.go | 5 +++ usage.go | 3 +- 8 files changed, 175 insertions(+), 92 deletions(-) create mode 100644 passageout.go diff --git a/config.go b/config.go index 71a12db..5347ccb 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,7 @@ type outputMode int const ( outModeHTML outputMode = iota + outModeJSON outModeTwee3 outModeTwee1 outModeTwine2Archive @@ -132,6 +133,7 @@ func newConfig() *config { options.Add("format", "-f=s|--format=s") options.Add("head", "--head=s") options.Add("help", "-h|--help") + options.Add("json", "-j|--json") options.Add("listcharsets", "--list-charsets") options.Add("listformats", "--list-formats") options.Add("logfiles", "--log-files") @@ -164,6 +166,8 @@ func newConfig() *config { c.headFile = val.(string) case "help": usage() + case "json": + c.outMode = outModeJSON case "listcharsets": usageCharsets() case "listformats": diff --git a/passage.go b/passage.go index 81ddc46..61ce681 100644 --- a/passage.go +++ b/passage.go @@ -8,7 +8,6 @@ package main import ( // standard packages - "fmt" "regexp" "strings" @@ -35,7 +34,7 @@ var infoPassages = []string{ // Compilers: Twine/Twee 1.4+, Twee2, & Tweego. "StorySettings", - // Compilers: Tweego & (whatever Dan Cox's compiler is called). + // Compilers: Tweego, Extwee, & others. "StoryData", // Compilers: Twine/Twee 1.4+ & Twee2. @@ -143,95 +142,6 @@ func (p *passage) isStoryPassage() bool { return !p.hasInfoName() && !p.hasInfoTags() } -func (p *passage) toTwee(outMode outputMode) string { - var output string - if outMode == outModeTwee3 { - output = ":: " + tweeEscapeString(p.name) - if len(p.tags) > 0 { - output += " [" + tweeEscapeString(strings.Join(p.tags, " ")) + "]" - } - if p.hasAnyMetadata() { - output += " " + string(p.marshalMetadata()) - } - } else { - output = ":: " + p.name - if len(p.tags) > 0 { - output += " [" + strings.Join(p.tags, " ") + "]" - } - } - output += "\n" - if len(p.text) > 0 { - output += p.text + "\n" - } - output += "\n\n" - return output -} - -func (p *passage) toPassagedata(pid uint) string { - var ( - position string - size string - ) - if p.hasMetadataPosition() { - position = p.metadata.position - } else { - // No position metadata, so generate something sensible on the fly. - x := pid % 10 - y := pid / 10 - if x == 0 { - x = 10 - } else { - y++ - } - position = fmt.Sprintf("%d,%d", x*125-25, y*125-25) - } - if p.hasMetadataSize() { - size = p.metadata.size - } else { - // No size metadata, so default to the normal size. - size = "100,100" - } - - /* - - */ - return fmt.Sprintf(`%s`, - pid, - attrEscapeString(p.name), - attrEscapeString(strings.Join(p.tags, " ")), - attrEscapeString(position), - attrEscapeString(size), - htmlEscapeString(p.text), - ) -} - -func (p *passage) toTiddler(pid uint) string { - var position string - if p.hasMetadataPosition() { - position = p.metadata.position - } else { - // No position metadata, so generate something sensible on the fly. - x := pid % 10 - y := pid / 10 - if x == 0 { - x = 10 - } else { - y++ - } - position = fmt.Sprintf("%d,%d", x*140-130, y*140-130) - } - - /* -
- */ - return fmt.Sprintf(`
%s
`, - attrEscapeString(p.name), - attrEscapeString(strings.Join(p.tags, " ")), - attrEscapeString(position), - tiddlerEscapeString(p.text), - ) -} - func (p *passage) countWords() uint64 { text := p.text diff --git a/passagedata.go b/passagedata.go index 182117a..a222a3c 100644 --- a/passagedata.go +++ b/passagedata.go @@ -10,6 +10,12 @@ import ( "encoding/json" ) +type passageJSON struct { + Name string `json:"name"` + Tags []string `json:"tags,omitempty"` + Text string `json:"text"` +} + type passageMetadataJSON struct { Position string `json:"position,omitempty"` // Twine 2 (`position`) & Twine 1 (`twine-position`). Size string `json:"size,omitempty"` // Twine 2 (`size`). diff --git a/passageout.go b/passageout.go new file mode 100644 index 0000000..912401c --- /dev/null +++ b/passageout.go @@ -0,0 +1,110 @@ +/* + Copyright © 2014–2021 Thomas Michael Edwards. All rights reserved. + Use of this source code is governed by a Simplified BSD License which + can be found in the LICENSE file. +*/ + +package main + +import ( + // standard packages + "fmt" + "strings" +) + +func (p *passage) toJSON() *passageJSON { + return &passageJSON{ + Name: p.name, + Tags: p.tags, + Text: p.text, + } +} + +func (p *passage) toTwee(outMode outputMode) string { + var output string + if outMode == outModeTwee3 { + output = ":: " + tweeEscapeString(p.name) + if len(p.tags) > 0 { + output += " [" + tweeEscapeString(strings.Join(p.tags, " ")) + "]" + } + if p.hasAnyMetadata() { + output += " " + string(p.marshalMetadata()) + } + } else { + output = ":: " + p.name + if len(p.tags) > 0 { + output += " [" + strings.Join(p.tags, " ") + "]" + } + } + output += "\n" + if len(p.text) > 0 { + output += p.text + "\n" + } + output += "\n\n" + return output +} + +func (p *passage) toPassagedata(pid uint) string { + var ( + position string + size string + ) + if p.hasMetadataPosition() { + position = p.metadata.position + } else { + // No position metadata, so generate something sensible on the fly. + x := pid % 10 + y := pid / 10 + if x == 0 { + x = 10 + } else { + y++ + } + position = fmt.Sprintf("%d,%d", x*125-25, y*125-25) + } + if p.hasMetadataSize() { + size = p.metadata.size + } else { + // No size metadata, so default to the normal size. + size = "100,100" + } + + /* + + */ + return fmt.Sprintf(`%s`, + pid, + attrEscapeString(p.name), + attrEscapeString(strings.Join(p.tags, " ")), + attrEscapeString(position), + attrEscapeString(size), + htmlEscapeString(p.text), + ) +} + +func (p *passage) toTiddler(pid uint) string { + var position string + if p.hasMetadataPosition() { + position = p.metadata.position + } else { + // No position metadata, so generate something sensible on the fly. + x := pid % 10 + y := pid / 10 + if x == 0 { + x = 10 + } else { + y++ + } + position = fmt.Sprintf("%d,%d", x*140-130, y*140-130) + } + + /* +
+ */ + return fmt.Sprintf(`
%s
`, + attrEscapeString(p.name), + attrEscapeString(strings.Join(p.tags, " ")), + attrEscapeString(position), + tiddlerEscapeString(p.text), + ) +} diff --git a/storydata.go b/storydata.go index 45108c2..1dd974f 100644 --- a/storydata.go +++ b/storydata.go @@ -13,6 +13,18 @@ import ( "strings" ) +type storyJSON struct { + Name string `json:"name"` + Ifid string `json:"ifid,omitempty"` + Start string `json:"start,omitempty"` + Options []string `json:"options,omitempty"` + Format string `json:"format,omitempty"` + FormatVersion string `json:"format-version,omitempty"` + Creator string `json:"creator,omitempty"` + CreatorVersion string `json:"creator-version,omitempty"` + Passages []*passageJSON `json:"passages"` +} + type storyDataJSON struct { Ifid string `json:"ifid,omitempty"` Format string `json:"format,omitempty"` diff --git a/storyout.go b/storyout.go index 42a05d0..5018a5a 100644 --- a/storyout.go +++ b/storyout.go @@ -8,6 +8,7 @@ package main import ( "bytes" + "encoding/json" "fmt" "log" "os" @@ -17,6 +18,40 @@ import ( "time" ) +func (s *story) toJSON(startName string) []byte { + var passages = make([]*passageJSON, 0, 32) + for _, p := range s.passages { + if p.name == "StoryTitle" || p.name == "StoryData" { + continue + } + + passages = append(passages, p.toJSON()) + } + + marshaled, err := json.MarshalIndent( + &storyJSON{ + s.name, + s.ifid, + startName, + twine2OptionsMapToSlice(s.twine2.options), + s.twine2.format, + s.twine2.formatVersion, + strings.Title(tweegoName), + tweegoVersion.Version(), + passages, + }, + "", + "\t", + ) + if err != nil { + // NOTE: We should never be able to see an error here. If we do, + // then something truly exceptional—in a bad way—has happened, so + // we get our panic on. + panic(err) + } + return marshaled +} + func (s *story) toTwee(outMode outputMode) []byte { var data []byte for _, p := range s.passages { diff --git a/tweego.go b/tweego.go index 4c4a6a2..cbf5d58 100644 --- a/tweego.go +++ b/tweego.go @@ -64,6 +64,11 @@ func buildOutput(c *config) *story { // Write the output. switch c.outMode { + case outModeJSON: + // Write out the project as JSON. + if _, err := fileWriteAll(c.outFile, alignRecordSeparators(s.toJSON(c.startName))); err != nil { + log.Fatalf(`error: %s`, err.Error()) + } case outModeTwee3, outModeTwee1: // Write out the project as Twee source. if _, err := fileWriteAll(c.outFile, alignRecordSeparators(s.toTwee(c.outMode))); err != nil { diff --git a/usage.go b/usage.go index 180f4df..79f8c18 100644 --- a/usage.go +++ b/usage.go @@ -42,6 +42,7 @@ Options: -h, --help Print this help, then exit. --head=FILE Name of the file whose contents will be appended as-is to the element of the compiled HTML. + -j, --json Output JSON, instead of compiled HTML. --list-charsets List the supported input character sets, then exit. --list-formats List the available story formats, then exit. --log-files Log the processed input files. @@ -121,7 +122,7 @@ func usageVersion() { fmt.Fprintf(os.Stderr, "\n%s, %s\n", tweegoName, tweegoVersion) fmt.Fprint(os.Stderr, ` Tweego (a Twee compiler in Go) [http://www.motoslave.net/tweego/] -Copyright (c) 2014-2020 Thomas Michael Edwards. All rights reserved. +Copyright (c) 2014-2021 Thomas Michael Edwards. All rights reserved. `) os.Exit(1)