ADD: Add JSON output (-j, --json).

This commit is contained in:
Thomas M. Edwards 2021-02-10 16:22:52 -06:00
parent 33a6da381c
commit 3a8dfb2344
8 changed files with 175 additions and 92 deletions

View file

@ -22,6 +22,7 @@ type outputMode int
const ( const (
outModeHTML outputMode = iota outModeHTML outputMode = iota
outModeJSON
outModeTwee3 outModeTwee3
outModeTwee1 outModeTwee1
outModeTwine2Archive outModeTwine2Archive
@ -132,6 +133,7 @@ func newConfig() *config {
options.Add("format", "-f=s|--format=s") options.Add("format", "-f=s|--format=s")
options.Add("head", "--head=s") options.Add("head", "--head=s")
options.Add("help", "-h|--help") options.Add("help", "-h|--help")
options.Add("json", "-j|--json")
options.Add("listcharsets", "--list-charsets") options.Add("listcharsets", "--list-charsets")
options.Add("listformats", "--list-formats") options.Add("listformats", "--list-formats")
options.Add("logfiles", "--log-files") options.Add("logfiles", "--log-files")
@ -164,6 +166,8 @@ func newConfig() *config {
c.headFile = val.(string) c.headFile = val.(string)
case "help": case "help":
usage() usage()
case "json":
c.outMode = outModeJSON
case "listcharsets": case "listcharsets":
usageCharsets() usageCharsets()
case "listformats": case "listformats":

View file

@ -8,7 +8,6 @@ package main
import ( import (
// standard packages // standard packages
"fmt"
"regexp" "regexp"
"strings" "strings"
@ -35,7 +34,7 @@ var infoPassages = []string{
// Compilers: Twine/Twee 1.4+, Twee2, & Tweego. // Compilers: Twine/Twee 1.4+, Twee2, & Tweego.
"StorySettings", "StorySettings",
// Compilers: Tweego & (whatever Dan Cox's compiler is called). // Compilers: Tweego, Extwee, & others.
"StoryData", "StoryData",
// Compilers: Twine/Twee 1.4+ & Twee2. // Compilers: Twine/Twee 1.4+ & Twee2.
@ -143,95 +142,6 @@ func (p *passage) isStoryPassage() bool {
return !p.hasInfoName() && !p.hasInfoTags() 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"
}
/*
<tw-passagedata pid="…" name="…" tags="…" position="…" size="…"></tw-passagedata>
*/
return fmt.Sprintf(`<tw-passagedata pid="%d" name=%q tags=%q position=%q size=%q>%s</tw-passagedata>`,
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)
}
/*
<div tiddler="…" tags="…" created="…" modifier="…" twine-position="…"></div>
*/
return fmt.Sprintf(`<div tiddler=%q tags=%q twine-position=%q>%s</div>`,
attrEscapeString(p.name),
attrEscapeString(strings.Join(p.tags, " ")),
attrEscapeString(position),
tiddlerEscapeString(p.text),
)
}
func (p *passage) countWords() uint64 { func (p *passage) countWords() uint64 {
text := p.text text := p.text

View file

@ -10,6 +10,12 @@ import (
"encoding/json" "encoding/json"
) )
type passageJSON struct {
Name string `json:"name"`
Tags []string `json:"tags,omitempty"`
Text string `json:"text"`
}
type passageMetadataJSON struct { type passageMetadataJSON struct {
Position string `json:"position,omitempty"` // Twine 2 (`position`) & Twine 1 (`twine-position`). Position string `json:"position,omitempty"` // Twine 2 (`position`) & Twine 1 (`twine-position`).
Size string `json:"size,omitempty"` // Twine 2 (`size`). Size string `json:"size,omitempty"` // Twine 2 (`size`).

110
passageout.go Normal file
View file

@ -0,0 +1,110 @@
/*
Copyright © 20142021 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"
}
/*
<tw-passagedata pid="…" name="…" tags="…" position="…" size="…"></tw-passagedata>
*/
return fmt.Sprintf(`<tw-passagedata pid="%d" name=%q tags=%q position=%q size=%q>%s</tw-passagedata>`,
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)
}
/*
<div tiddler="…" tags="…" created="…" modifier="…" twine-position="…"></div>
*/
return fmt.Sprintf(`<div tiddler=%q tags=%q twine-position=%q>%s</div>`,
attrEscapeString(p.name),
attrEscapeString(strings.Join(p.tags, " ")),
attrEscapeString(position),
tiddlerEscapeString(p.text),
)
}

View file

@ -13,6 +13,18 @@ import (
"strings" "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 { type storyDataJSON struct {
Ifid string `json:"ifid,omitempty"` Ifid string `json:"ifid,omitempty"`
Format string `json:"format,omitempty"` Format string `json:"format,omitempty"`

View file

@ -8,6 +8,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"log" "log"
"os" "os"
@ -17,6 +18,40 @@ import (
"time" "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 { func (s *story) toTwee(outMode outputMode) []byte {
var data []byte var data []byte
for _, p := range s.passages { for _, p := range s.passages {

View file

@ -64,6 +64,11 @@ func buildOutput(c *config) *story {
// Write the output. // Write the output.
switch c.outMode { 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: case outModeTwee3, outModeTwee1:
// Write out the project as Twee source. // Write out the project as Twee source.
if _, err := fileWriteAll(c.outFile, alignRecordSeparators(s.toTwee(c.outMode))); err != nil { if _, err := fileWriteAll(c.outFile, alignRecordSeparators(s.toTwee(c.outMode))); err != nil {

View file

@ -42,6 +42,7 @@ Options:
-h, --help Print this help, then exit. -h, --help Print this help, then exit.
--head=FILE Name of the file whose contents will be appended --head=FILE Name of the file whose contents will be appended
as-is to the <head> element of the compiled HTML. as-is to the <head> element of the compiled HTML.
-j, --json Output JSON, instead of compiled HTML.
--list-charsets List the supported input character sets, then exit. --list-charsets List the supported input character sets, then exit.
--list-formats List the available story formats, then exit. --list-formats List the available story formats, then exit.
--log-files Log the processed input files. --log-files Log the processed input files.
@ -121,7 +122,7 @@ func usageVersion() {
fmt.Fprintf(os.Stderr, "\n%s, %s\n", tweegoName, tweegoVersion) fmt.Fprintf(os.Stderr, "\n%s, %s\n", tweegoName, tweegoVersion)
fmt.Fprint(os.Stderr, ` fmt.Fprint(os.Stderr, `
Tweego (a Twee compiler in Go) [http://www.motoslave.net/tweego/] 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) os.Exit(1)