/*
Copyright © 2014–2020 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"
"regexp"
"strings"
// external packages
"golang.org/x/text/unicode/norm"
)
// Info passages are passages which contain solely structural data, metadata,
// and code, rather than any actual story content.
var infoPassages = []string{
// Story formats: Twine 1.4+ vanilla & SugarCube.
"StoryAuthor", "StoryInit", "StoryMenu", "StorySubtitle", "StoryTitle",
// Story formats: SugarCube.
"PassageReady", "PassageDone", "PassageHeader", "PassageFooter", "StoryBanner", "StoryCaption",
// Story formats: SugarCube (v1 only).
"MenuOptions", "MenuShare", "MenuStory",
// Story formats: SugarCube (v2 only).
"StoryInterface", "StoryShare",
// Story formats: Twine 1.4+ vanilla.
// Compilers: Twine/Twee 1.4+, Twee2, & Tweego.
"StorySettings",
// Compilers: Tweego & (whatever Dan Cox's compiler is called).
"StoryData",
// Compilers: Twine/Twee 1.4+ & Twee2.
"StoryIncludes",
}
type passageMetadata struct {
position string // Unused by Tweego. Twine 1 & 2 passage block X and Y coordinates CSV.
size string // Unused by Tweego. Twine 2 passage block width and height CSV.
}
type passage struct {
// Core.
name string
tags []string
text string
// Compiler metadata.
metadata *passageMetadata
}
func newPassage(name string, tags []string, source string) *passage {
return &passage{
name: name,
tags: tags,
text: source,
}
}
func (p *passage) equals(second passage) bool {
return p.text == second.text
}
func (p *passage) tagsHas(needle string) bool {
if len(p.tags) > 0 {
for _, tag := range p.tags {
if tag == needle {
return true
}
}
}
return false
}
func (p *passage) tagsHasAny(needles ...string) bool {
if len(p.tags) > 0 {
for _, tag := range p.tags {
for _, needle := range needles {
if tag == needle {
return true
}
}
}
}
return false
}
func (p *passage) tagsContains(needle string) bool {
if len(p.tags) > 0 {
for _, tag := range p.tags {
if strings.Contains(tag, needle) {
return true
}
}
}
return false
}
func (p *passage) tagsStartsWith(needle string) bool {
if len(p.tags) > 0 {
for _, tag := range p.tags {
if strings.HasPrefix(tag, needle) {
return true
}
}
}
return false
}
func (p *passage) hasMetadataPosition() bool {
return p.metadata != nil && p.metadata.position != ""
}
func (p *passage) hasMetadataSize() bool {
return p.metadata != nil && p.metadata.size != ""
}
func (p *passage) hasAnyMetadata() bool {
return p.metadata != nil && (p.metadata.position != "" || p.metadata.size != "")
}
func (p *passage) hasInfoTags() bool {
return p.tagsHasAny("annotation", "script", "stylesheet", "widget") || p.tagsStartsWith("Twine.")
}
func (p *passage) hasInfoName() bool {
return stringSliceContains(infoPassages, p.name)
}
func (p *passage) isInfoPassage() bool {
return p.hasInfoName() || p.hasInfoTags()
}
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"
}
/*