mirror of
https://github.com/tmedwards/tweego.git
synced 2025-07-04 21:50:33 -04:00
commit
9ea2fabb84
16 changed files with 124 additions and 65 deletions
49
README.md
49
README.md
|
@ -6,6 +6,51 @@ See [Tweego's documentation](http://www.motoslave.net/tweego/docs/) for informat
|
||||||
|
|
||||||
Tweego has a Trello board, [Tweego TODO](https://trello.com/b/l5xuRzFu). Feel free to comment.
|
Tweego has a Trello board, [Tweego TODO](https://trello.com/b/l5xuRzFu). Feel free to comment.
|
||||||
|
|
||||||
## NOTICE
|
## INSTALLATION
|
||||||
|
|
||||||
This is the initial commit to this GitHub repository, which is a migration from the old Bitbucket repository. It is based on the v2.0.0 release and is not suitable for compilation as-is due to import path differences. The first release from this repository will address that problem.
|
You may either download one of the precompiled binaries from [Tweego's website](http://www.motoslave.net/tweego/), which are available in both 32- and 64-bit versions for multiple operating systems, or build Tweego from source (see **BUILDING FROM SOURCE** below).
|
||||||
|
|
||||||
|
## BUILDING FROM SOURCE
|
||||||
|
|
||||||
|
If you want to build Tweego from scratch, rather than grabbing one of the precompiled binaries off of its website, then these instructions are for you.
|
||||||
|
|
||||||
|
Tweego is written in the Go programming language, so you'll need to install it, if you don't already have it. Additionally, to retrieve Go packages—like Tweego and its dependencies—from source control repositories, you'll need to install Git.
|
||||||
|
|
||||||
|
1. [Download and install the Go programming language (want ≥v1.13)](http://golang.org/doc/install)
|
||||||
|
2. [Download and install the Git source control management tool](https://git-scm.com/)
|
||||||
|
|
||||||
|
Once all the tooling is installed and set up, the next step is to fetch the Tweego source code. Open a shell to wherever you wish to store the code and run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/tmedwards/tweego.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, change to the directory that the previous command created:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd tweego
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, fetch Tweego's dependencies via the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now have Tweego and all its dependencies downloaded, so you may now compile and install it to your `GOPATH` by running the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
go install
|
||||||
|
```
|
||||||
|
|
||||||
|
Assuming that completed with no errors, your compiled Tweego binary should be in your `GOPATH`'s `bin` directory. To run Tweego you'll need to either have added your `GOPATH` `bin` to your `PATH` environment variable—this was likely done for you automatically during the installation of Go—or copy the binary to an existing directory within your `PATH`.
|
||||||
|
|
||||||
|
Alternatively. If you just want to compile Tweego, so that you can manually copy the binary to wherever you wish, use the following command instead:
|
||||||
|
|
||||||
|
```
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
Assuming that completed with no errors, your compiled Tweego binary should be in the current directory—likely named either `tweego` or `tweego.exe` depending on your OS.
|
||||||
|
|
||||||
|
Finally, see [Tweego's documentation](http://www.motoslave.net/tweego/docs/) for information on how to set it up and use it.
|
||||||
|
|
23
config.go
23
config.go
|
@ -12,7 +12,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
// internal packages
|
// internal packages
|
||||||
"bitbucket.org/tmedwards/tweego/internal/option"
|
"github.com/tmedwards/tweego/internal/option"
|
||||||
// external packages
|
// external packages
|
||||||
"github.com/paulrosania/go-charset/charset"
|
"github.com/paulrosania/go-charset/charset"
|
||||||
)
|
)
|
||||||
|
@ -44,11 +44,12 @@ type config struct {
|
||||||
outMode outputMode // output mode
|
outMode outputMode // output mode
|
||||||
|
|
||||||
formats storyFormatsMap // map of all enumerated story formats
|
formats storyFormatsMap // map of all enumerated story formats
|
||||||
|
logFiles bool // log input files
|
||||||
|
logStats bool // log story statistics
|
||||||
testMode bool // enable test mode
|
testMode bool // enable test mode
|
||||||
|
trim bool // enable passage trimming
|
||||||
twee2Compat bool // enable Twee2 header extension compatibility mode
|
twee2Compat bool // enable Twee2 header extension compatibility mode
|
||||||
watchFiles bool // enable filesystem watching
|
watchFiles bool // enable filesystem watching
|
||||||
logStats bool // log story statistics
|
|
||||||
logFiles bool // log input files
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -56,6 +57,7 @@ const (
|
||||||
defaultOutFile = "-" // <stdout>
|
defaultOutFile = "-" // <stdout>
|
||||||
defaultOutMode = outModeHTML
|
defaultOutMode = outModeHTML
|
||||||
defaultStartName = "Start"
|
defaultStartName = "Start"
|
||||||
|
defaultTrimState = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// newConfig creates a new config instance
|
// newConfig creates a new config instance
|
||||||
|
@ -97,20 +99,10 @@ func newConfig() *config {
|
||||||
common: common{formatID: defaultFormatID, startName: defaultStartName},
|
common: common{formatID: defaultFormatID, startName: defaultStartName},
|
||||||
outFile: defaultOutFile,
|
outFile: defaultOutFile,
|
||||||
outMode: defaultOutMode,
|
outMode: defaultOutMode,
|
||||||
|
trim: defaultTrimState,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge values from the environment variables.
|
// Merge values from the environment variables.
|
||||||
// /*
|
|
||||||
// LEGACY
|
|
||||||
// */
|
|
||||||
// for _, env := range []string{"TWEEGO_CHARSET", "TWEEGO_FORMAT"} {
|
|
||||||
// if _, exists := os.LookupEnv(env); exists {
|
|
||||||
// log.Printf("warning: Detected obsolete environment variable %q. Please remove it from your environment.", env)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// /*
|
|
||||||
// END LEGACY
|
|
||||||
// */
|
|
||||||
if env := os.Getenv("TWEEGO_PATH"); env != "" {
|
if env := os.Getenv("TWEEGO_PATH"); env != "" {
|
||||||
formatDirs = append(formatDirs, filepath.SplitList(env)...)
|
formatDirs = append(formatDirs, filepath.SplitList(env)...)
|
||||||
}
|
}
|
||||||
|
@ -144,6 +136,7 @@ func newConfig() *config {
|
||||||
options.Add("logfiles", "--log-files")
|
options.Add("logfiles", "--log-files")
|
||||||
options.Add("logstats", "-l|--log-stats")
|
options.Add("logstats", "-l|--log-stats")
|
||||||
options.Add("module", "-m=s+|--module=s+")
|
options.Add("module", "-m=s+|--module=s+")
|
||||||
|
options.Add("no_trim", "--no-trim")
|
||||||
options.Add("output", "-o=s|--output=s")
|
options.Add("output", "-o=s|--output=s")
|
||||||
options.Add("start", "-s=s|--start=s")
|
options.Add("start", "-s=s|--start=s")
|
||||||
options.Add("test", "-t|--test")
|
options.Add("test", "-t|--test")
|
||||||
|
@ -180,6 +173,8 @@ func newConfig() *config {
|
||||||
c.logStats = true
|
c.logStats = true
|
||||||
case "module":
|
case "module":
|
||||||
c.modulePaths = append(c.modulePaths, val.([]string)...)
|
c.modulePaths = append(c.modulePaths, val.([]string)...)
|
||||||
|
case "no_trim":
|
||||||
|
c.trim = false
|
||||||
case "output":
|
case "output":
|
||||||
c.outFile = val.(string)
|
c.outFile = val.(string)
|
||||||
case "start":
|
case "start":
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
This is a collection of tips, from how to avoid pitfalls to best practices.
|
This is a collection of tips, from how to avoid pitfalls to best practices.
|
||||||
|
|
||||||
Suggestions for new entries may be submitted by [creating a new issue](https://bitbucket.org/tmedwards/tweego/issues?status=new&status=open) at Tweego's [code repository](https://bitbucket.org/tmedwards/tweego/). **NOTE:** Acceptance of submissions ***is not*** guaranteed.
|
Suggestions for new entries may be submitted by [creating a new issue](https://github.com/tmedwards/tweego/issues) at Tweego's [code repository](https://github.com/tmedwards/tweego). **NOTE:** Acceptance of submissions ***is not*** guaranteed.
|
||||||
|
|
||||||
|
|
||||||
<!-- ***************************************************************************
|
<!-- ***************************************************************************
|
||||||
|
@ -14,7 +14,7 @@ Suggestions for new entries may be submitted by [creating a new issue](https://b
|
||||||
<span id="faq-and-tips-avoid-processing-files"></span>
|
<span id="faq-and-tips-avoid-processing-files"></span>
|
||||||
## Avoid processing files
|
## Avoid processing files
|
||||||
|
|
||||||
The way to avoid having Tweego process files is to not pass it the files in the first place—i.e. keep the files in question separate from the files you want Tweego to compile.
|
The way to avoid having Tweego process files is to not pass it the files in the first place—i.e., keep the files in question separate from the files you want Tweego to compile.
|
||||||
|
|
||||||
Using image files as an example, I would generally recommend a directory structure something like:
|
Using image files as an example, I would generally recommend a directory structure something like:
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,8 @@ The various methods for specifying configuration settings cascade in the followi
|
||||||
<dd>
|
<dd>
|
||||||
<p>Path(s) to search for story formats. The value should be a list of directories to search for story formats. You may specify one directory or several. The format is exactly the same as any other <em>path type</em> environment variable for your operating system.</p>
|
<p>Path(s) to search for story formats. The value should be a list of directories to search for story formats. You may specify one directory or several. The format is exactly the same as any other <em>path type</em> environment variable for your operating system.</p>
|
||||||
<p class="tip" role="note"><b>Tip:</b> Setting <var>TWEEGO_PATH</var> is only necessary if you intend to place your story formats outside of the directories normally searched by Tweego. See <a href="#getting-started-story-formats-search-directories">Search Directories</a> for more information.</p>
|
<p class="tip" role="note"><b>Tip:</b> Setting <var>TWEEGO_PATH</var> is only necessary if you intend to place your story formats outside of the directories normally searched by Tweego. See <a href="#getting-started-story-formats-search-directories">Search Directories</a> for more information.</p>
|
||||||
<p role="note"><b>Note:</b> To separate multiple directories within <em>path</em> variables, Unix-like operating systems use the colon, while Windows uses the semi-colon. Only relevant if you intend to specify multiple directories.</p>
|
<p role="note"><b>Note:</b> To separate multiple directories within <em>path</em> variables, Unix/Unix-like operating systems use the colon (<kbd>:</kbd>), while Windows uses the semi-colon (<kbd>;</kbd>). Only relevant if you intend to specify multiple directories.</p>
|
||||||
<p><strong>Unix-y examples</strong></p>
|
<p><strong>Unix/Unix-like examples</strong></p>
|
||||||
<p>If you wanted Tweego to search <code>/usr/local/storyformats</code>, then you'd set <code>TWEEGO_PATH</code> to:</p>
|
<p>If you wanted Tweego to search <code>/usr/local/storyformats</code>, then you'd set <code>TWEEGO_PATH</code> to:</p>
|
||||||
<pre><code>/usr/local/storyformats</code></pre>
|
<pre><code>/usr/local/storyformats</code></pre>
|
||||||
<p>If you wanted Tweego to search <code>/storyformats</code> and <code>/usr/local/storyformats</code>, then you'd set <code>TWEEGO_PATH</code> to:</p>
|
<p>If you wanted Tweego to search <code>/storyformats</code> and <code>/usr/local/storyformats</code>, then you'd set <code>TWEEGO_PATH</code> to:</p>
|
||||||
|
@ -106,7 +106,7 @@ When Tweego is run, it finds story formats to use by searching the following dir
|
||||||
4. The directories specified via the <var>TWEEGO_PATH</var> environment variable. See <a href="#getting-started-environment-variables">Environment Variables</a> for more information.
|
4. The directories specified via the <var>TWEEGO_PATH</var> environment variable. See <a href="#getting-started-environment-variables">Environment Variables</a> for more information.
|
||||||
|
|
||||||
<p role="note"><b>Note:</b>
|
<p role="note"><b>Note:</b>
|
||||||
For legacy compatibility, the following directories are also checked during steps #1–3: <kbd>story-formats</kbd>, <kbd>storyFormats</kbd>, and <kbd>targets</kbd>. You are encouraged to use one of the directory names listed above instead.
|
For legacy compatibility, the following directories are also checked during steps #1–3: <kbd>story-formats</kbd>, <kbd>storyFormats</kbd>, and <kbd>targets</kbd>. You are <strong><em>strongly encouraged</em></strong> to use one of the directory names listed above instead.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="warning" role="note"><b>Warning:</b>
|
<p class="warning" role="note"><b>Warning:</b>
|
||||||
|
|
|
@ -25,7 +25,7 @@ The names of all special passages and tags listed herein are case sensitive, thu
|
||||||
<span id="special-passages-start"></span>
|
<span id="special-passages-start"></span>
|
||||||
### `Start`
|
### `Start`
|
||||||
|
|
||||||
The `Start` passage will, by default, be used as the starting passage—i.e. the first normal passage displayed to the player. That behavior may be overridden via either the <var>start</var> property from the [`StoryData` passage](#special-passages-storydata) or the start command line option (<kbd>-s NAME</kbd>, <kbd>--start=NAME</kbd>).
|
The `Start` passage will, by default, be used as the starting passage—i.e., the first normal passage displayed to the player. That behavior may be overridden via either the <var>start</var> property from the [`StoryData` passage](#special-passages-storydata) or the start command line option (<kbd>-s NAME</kbd>, <kbd>--start=NAME</kbd>).
|
||||||
|
|
||||||
<p class="tip" role="note"><b>Tip:</b>
|
<p class="tip" role="note"><b>Tip:</b>
|
||||||
It is <strong><em>strongly recommended</em></strong> that you simply use the default starting name, <code>Start</code>, when beginning new projects.
|
It is <strong><em>strongly recommended</em></strong> that you simply use the default starting name, <code>Start</code>, when beginning new projects.
|
||||||
|
|
|
@ -59,6 +59,10 @@ Where <code>[options]</code> are mostly optional configuration flags—see [Opti
|
||||||
<p role="note"><b>Note:</b> Unsupported when watch mode (<kbd>-w</kbd>, <kbd>--watch</kbd>) is enabled.</p>
|
<p role="note"><b>Note:</b> Unsupported when watch mode (<kbd>-w</kbd>, <kbd>--watch</kbd>) is enabled.</p>
|
||||||
</dd>
|
</dd>
|
||||||
<dt><kbd>-m SRC</kbd>, <kbd>--module=SRC</kbd></dt><dd>Module sources (repeatable); may consist of supported files and/or directories to recursively search for such files. Each file will be wrapped within the appropriate markup and bundled into the <head> element of the compiled HTML. Supported files: <code>.css</code>, <code>.js</code>, <code>.otf</code>, <code>.ttf</code>, <code>.woff</code>, <code>.woff2</code>.</dd>
|
<dt><kbd>-m SRC</kbd>, <kbd>--module=SRC</kbd></dt><dd>Module sources (repeatable); may consist of supported files and/or directories to recursively search for such files. Each file will be wrapped within the appropriate markup and bundled into the <head> element of the compiled HTML. Supported files: <code>.css</code>, <code>.js</code>, <code>.otf</code>, <code>.ttf</code>, <code>.woff</code>, <code>.woff2</code>.</dd>
|
||||||
|
<dt><kbd>--no-trim</kbd></dt><dd>
|
||||||
|
<p>Do not trim whitespace surrounding passages—i.e., whitespace preceding and trailing the actual text of the passage. By default, such whitespace is removed when processing passages.</p>
|
||||||
|
<p role="note"><b>Note:</b> It is recommended that you do not disable passage trimming.</p>
|
||||||
|
</dd>
|
||||||
<dt><kbd>-o FILE</kbd>, <kbd>--output=FILE</kbd></dt><dd>Name of the output file (default: <kbd>-</kbd>; i.e., <a href="https://en.wikipedia.org/wiki/Standard_streams" target="_blank"><i>standard output</i></a>).</dd>
|
<dt><kbd>-o FILE</kbd>, <kbd>--output=FILE</kbd></dt><dd>Name of the output file (default: <kbd>-</kbd>; i.e., <a href="https://en.wikipedia.org/wiki/Standard_streams" target="_blank"><i>standard output</i></a>).</dd>
|
||||||
<dt><kbd>-s NAME</kbd>, <kbd>--start=NAME</kbd></dt><dd>Name of the starting passage (default: the passage set by the story data, elsewise <code>"Start"</code>).</dd>
|
<dt><kbd>-s NAME</kbd>, <kbd>--start=NAME</kbd></dt><dd>Name of the starting passage (default: the passage set by the story data, elsewise <code>"Start"</code>).</dd>
|
||||||
<dt><kbd>-t</kbd>, <kbd>--test</kbd></dt><dd>Compile in test mode; only for story formats in the Twine 2 style.</dd>
|
<dt><kbd>-t</kbd>, <kbd>--test</kbd></dt><dd>Compile in test mode; only for story formats in the Twine 2 style.</dd>
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
|
|
||||||
This documentation is a reference for [Tweego](http://www.motoslave.net/tweego/), a free (gratis and libre) command line compiler for [Twine/Twee](http://twinery.org/) story formats, written in [Go](http://golang.org/).
|
This documentation is a reference for [Tweego](http://www.motoslave.net/tweego/), a free (gratis and libre) command line compiler for [Twine/Twee](http://twinery.org/) story formats, written in [Go](http://golang.org/).
|
||||||
|
|
||||||
If you believe that you've found a bug in Tweego or simply wish to make a suggestion, you may do so by [creating a new issue](https://bitbucket.org/tmedwards/tweego/issues?status=new&status=open) at its [code repository](https://bitbucket.org/tmedwards/tweego/).
|
If you believe that you've found a bug in Tweego or simply wish to make a suggestion, you may do so by [creating a new issue](https://github.com/tmedwards/tweego/issues) at its [code repository](https://github.com/tmedwards/tweego).
|
||||||
|
|
14
escaping.go
14
escaping.go
|
@ -18,7 +18,7 @@ import (
|
||||||
var attrEscaper = strings.NewReplacer(
|
var attrEscaper = strings.NewReplacer(
|
||||||
`&`, `&`,
|
`&`, `&`,
|
||||||
`"`, `"`,
|
`"`, `"`,
|
||||||
// FIXME: Keep the following? All markup we generate double quotes attribute
|
// QUESTION: Keep the following? All markup we generate double quotes attribute
|
||||||
// values, so escaping single quotes/apostrophes isn't actually necessary.
|
// values, so escaping single quotes/apostrophes isn't actually necessary.
|
||||||
`'`, `'`,
|
`'`, `'`,
|
||||||
)
|
)
|
||||||
|
@ -30,15 +30,14 @@ func attrEscapeString(s string) string {
|
||||||
return attrEscaper.Replace(s)
|
return attrEscaper.Replace(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape the minimum characters required for general HTML escaping—i.e. only
|
// Escape the minimum characters required for general HTML escaping—i.e., only
|
||||||
// the special characters (`&`, `<`, `>`, `"`, `'`).
|
// the special characters (`&`, `<`, `>`, `"`, `'`).
|
||||||
//
|
//
|
||||||
// NOTE: The following exists because `html.EscapeString()` converts double
|
// NOTE: The following exists because `html.EscapeString()` converts double
|
||||||
// quotes (`"`) to their decimal numeric character reference (`"`) rather
|
// quotes (`"`) to their decimal numeric character reference (`"`) rather
|
||||||
// than to their entity (`"`). While the behavior is entirely legal, and
|
// than to their entity (`"`). While the behavior is entirely legal, and
|
||||||
// browsers will happily accept the NCRs, a not insignificant amount of JavaScript
|
// browsers will happily accept the NCRs, a not insignificant amount of code in
|
||||||
// code does not expect it and will fail to properly unescape the NCR—expecting
|
// the wild only checks for `"` and will fail to properly unescape the NCR.
|
||||||
// only `"`.
|
|
||||||
//
|
//
|
||||||
// The primary special characters (`&`, `<`, `>`, `"`) should always be
|
// The primary special characters (`&`, `<`, `>`, `"`) should always be
|
||||||
// converted to their entity forms and never to an NCR form. Saving one byte
|
// converted to their entity forms and never to an NCR form. Saving one byte
|
||||||
|
@ -101,11 +100,6 @@ func tweeEscapeBytes(s []byte) []byte {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return []byte(nil)
|
return []byte(nil)
|
||||||
}
|
}
|
||||||
// e := bytes.Replace(s, []byte("\\"), []byte("\\\\"), -1)
|
|
||||||
// e = bytes.Replace(e, []byte("["), []byte("\\["), -1)
|
|
||||||
// e = bytes.Replace(e, []byte("]"), []byte("\\]"), -1)
|
|
||||||
// e = bytes.Replace(e, []byte("{"), []byte("\\{"), -1)
|
|
||||||
// e = bytes.Replace(e, []byte("}"), []byte("\\}"), -1)
|
|
||||||
|
|
||||||
// NOTE: The slices this will be used with will be short enough that
|
// NOTE: The slices this will be used with will be short enough that
|
||||||
// iterating a slice twice shouldn't be problematic. That said,
|
// iterating a slice twice shouldn't be problematic. That said,
|
||||||
|
|
21
formats.go
21
formats.go
|
@ -16,7 +16,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
// external packages
|
// external packages
|
||||||
"github.com/blang/semver"
|
"github.com/Masterminds/semver/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type twine2FormatJSON struct {
|
type twine2FormatJSON struct {
|
||||||
|
@ -213,9 +213,9 @@ func (m storyFormatsMap) getIDFromTwine2Name(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.name == name {
|
if f.name == name {
|
||||||
if v, err := semver.ParseTolerant(f.version); err == nil {
|
if have, err := semver.NewVersion(f.version); err == nil {
|
||||||
if found == nil || v.GT(*found) {
|
if found == nil || have.GreaterThan(found) {
|
||||||
found = &v
|
found = have
|
||||||
id = f.id
|
id = f.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,11 +231,10 @@ func (m storyFormatsMap) getIDFromTwine2NameAndVersion(name, version string) str
|
||||||
found *semver.Version
|
found *semver.Version
|
||||||
id string
|
id string
|
||||||
)
|
)
|
||||||
if v, err := semver.ParseTolerant(version); err == nil {
|
if v, err := semver.NewVersion(version); err == nil {
|
||||||
wanted = &v
|
wanted = v
|
||||||
} else {
|
} else {
|
||||||
log.Printf("warning: format %q: Auto-selecting greatest version; Could not parse version %q.", name, version)
|
log.Printf("warning: format %q: Auto-selecting greatest version; Could not parse version %q.", name, version)
|
||||||
wanted = &semver.Version{Major: 0, Minor: 0, Patch: 0}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range m {
|
for _, f := range m {
|
||||||
|
@ -244,10 +243,10 @@ func (m storyFormatsMap) getIDFromTwine2NameAndVersion(name, version string) str
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.name == name {
|
if f.name == name {
|
||||||
if v, err := semver.ParseTolerant(f.version); err == nil {
|
if have, err := semver.NewVersion(f.version); err == nil {
|
||||||
if wanted.Major == 0 || v.Major == wanted.Major && v.GTE(*wanted) {
|
if wanted == nil || have.Major() == wanted.Major() && have.Compare(wanted) > -1 {
|
||||||
if found == nil || v.GT(*found) {
|
if found == nil || have.GreaterThan(found) {
|
||||||
found = &v
|
found = have
|
||||||
id = f.id
|
id = f.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module github.com/tmedwards/tweego
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Masterminds/semver/v3 v3.0.3
|
||||||
|
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
||||||
|
github.com/radovskyb/watcher v1.0.7
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
|
||||||
|
golang.org/x/text v0.3.2
|
||||||
|
)
|
18
ifid.go
18
ifid.go
|
@ -13,11 +13,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IFIDs are defined in The Treaty of Babel.
|
// An IFID (Interactive Fiction IDentifier) uniquely identifies compiled
|
||||||
|
// projects. Most IFIDs are simply the string form of a v4 random UUID.
|
||||||
|
//
|
||||||
|
// IFIDs, in general, are defined within The Treaty of Babel.
|
||||||
// SEE: http://babel.ifarchive.org/
|
// SEE: http://babel.ifarchive.org/
|
||||||
//
|
//
|
||||||
// Most IFIDs, and certainly those generated by Tweego, are simply the
|
// Twine ecosystem IFIDs are defined within both the Twee 3 Specification
|
||||||
// string form of a v4 random UUID.
|
// and Twine 2 HTML Output Specification.
|
||||||
|
// SEE: https://github.com/iftechfoundation/twine-specs/
|
||||||
|
|
||||||
// newIFID generates a new IFID (UUID v4).
|
// newIFID generates a new IFID (UUID v4).
|
||||||
func newIFID() (string, error) {
|
func newIFID() (string, error) {
|
||||||
|
@ -35,8 +39,12 @@ func newIFID() (string, error) {
|
||||||
// validateIFID validates ifid or returns an error.
|
// validateIFID validates ifid or returns an error.
|
||||||
func validateIFID(ifid string) error {
|
func validateIFID(ifid string) error {
|
||||||
switch len(ifid) {
|
switch len(ifid) {
|
||||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
case 36 + 9: // UUID://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx//
|
case 36:
|
||||||
|
// no-op
|
||||||
|
|
||||||
|
// UUID://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx//
|
||||||
|
case 36 + 9:
|
||||||
if strings.ToUpper(ifid[:7]) != "UUID://" || ifid[43:] != "//" {
|
if strings.ToUpper(ifid[:7]) != "UUID://" || ifid[43:] != "//" {
|
||||||
return fmt.Errorf("invalid IFID UUID://…// format")
|
return fmt.Errorf("invalid IFID UUID://…// format")
|
||||||
}
|
}
|
||||||
|
|
3
story.go
3
story.go
|
@ -9,6 +9,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Twine 1 story metadata.
|
// Twine 1 story metadata.
|
||||||
|
@ -182,6 +183,8 @@ func (s *story) add(p *passage) {
|
||||||
log.Printf(`warning: Cannot unmarshal "StorySettings" special passage; %s.`, err.Error())
|
log.Printf(`warning: Cannot unmarshal "StorySettings" special passage; %s.`, err.Error())
|
||||||
}
|
}
|
||||||
case "StoryTitle":
|
case "StoryTitle":
|
||||||
|
// Rebuild the passage contents to trim erroneous whitespace surrounding the title.
|
||||||
|
p.text = strings.TrimSpace(p.text)
|
||||||
s.name = p.text
|
s.name = p.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
storyload.go
26
storyload.go
|
@ -14,10 +14,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
|
||||||
// internal packages
|
// internal packages
|
||||||
twee2 "bitbucket.org/tmedwards/tweego/internal/twee2compat"
|
twee2 "github.com/tmedwards/tweego/internal/twee2compat"
|
||||||
twlex "bitbucket.org/tmedwards/tweego/internal/tweelexer"
|
twlex "github.com/tmedwards/tweego/internal/tweelexer"
|
||||||
// external packages
|
// external packages
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
@ -32,11 +31,11 @@ func (s *story) load(filenames []string, c *config) {
|
||||||
switch normalizedFileExt(filename) {
|
switch normalizedFileExt(filename) {
|
||||||
// NOTE: The case values here should match those in `filesystem.go:knownFileType()`.
|
// NOTE: The case values here should match those in `filesystem.go:knownFileType()`.
|
||||||
case "tw", "twee":
|
case "tw", "twee":
|
||||||
if err := s.loadTwee(filename, c.encoding, c.twee2Compat); err != nil {
|
if err := s.loadTwee(filename, c.encoding, c.trim, c.twee2Compat); err != nil {
|
||||||
log.Fatalf("error: load %s: %s", filename, err.Error())
|
log.Fatalf("error: load %s: %s", filename, err.Error())
|
||||||
}
|
}
|
||||||
case "tw2", "twee2":
|
case "tw2", "twee2":
|
||||||
if err := s.loadTwee(filename, c.encoding, true); err != nil {
|
if err := s.loadTwee(filename, c.encoding, c.trim, true); err != nil {
|
||||||
log.Fatalf("error: load %s: %s", filename, err.Error())
|
log.Fatalf("error: load %s: %s", filename, err.Error())
|
||||||
}
|
}
|
||||||
case "htm", "html":
|
case "htm", "html":
|
||||||
|
@ -89,7 +88,7 @@ func (s *story) load(filenames []string, c *config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *story) loadTwee(filename, encoding string, twee2Compat bool) error {
|
func (s *story) loadTwee(filename, encoding string, trim, twee2Compat bool) error {
|
||||||
source, err := fileReadAllWithEncoding(filename, encoding)
|
source, err := fileReadAllWithEncoding(filename, encoding)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -109,12 +108,6 @@ ParseLoop:
|
||||||
for {
|
for {
|
||||||
p := &passage{}
|
p := &passage{}
|
||||||
for item, ok := lex.NextItem(); ok; item, ok = lex.NextItem() {
|
for item, ok := lex.NextItem(); ok; item, ok = lex.NextItem() {
|
||||||
// switch item.Type {
|
|
||||||
// case twlex.ItemEOF, twlex.ItemHeader:
|
|
||||||
// log.Println()
|
|
||||||
// }
|
|
||||||
// log.Printf("%v\n", item)
|
|
||||||
|
|
||||||
switch item.Type {
|
switch item.Type {
|
||||||
case twlex.ItemError:
|
case twlex.ItemError:
|
||||||
return fmt.Errorf("line %d: Malformed twee source; %s.", item.Line, item.Val)
|
return fmt.Errorf("line %d: Malformed twee source; %s.", item.Line, item.Val)
|
||||||
|
@ -157,8 +150,13 @@ ParseLoop:
|
||||||
}
|
}
|
||||||
|
|
||||||
case twlex.ItemContent:
|
case twlex.ItemContent:
|
||||||
// p.text = string(bytes.TrimSpace(item.Val))
|
if trim {
|
||||||
p.text = string(bytes.TrimRightFunc(item.Val, unicode.IsSpace))
|
// Trim whitespace surrounding (leading and trailing) passages.
|
||||||
|
p.text = string(bytes.TrimSpace(item.Val))
|
||||||
|
} else {
|
||||||
|
// Do not trim whitespace surrounding passages.
|
||||||
|
p.text = string(item.Val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastType = item.Type
|
lastType = item.Type
|
||||||
|
|
|
@ -26,7 +26,8 @@ func main() {
|
||||||
// Build the output and, possibly, log various stats.
|
// Build the output and, possibly, log various stats.
|
||||||
if c.watchFiles {
|
if c.watchFiles {
|
||||||
buildName := relPath(c.outFile)
|
buildName := relPath(c.outFile)
|
||||||
watchFilesystem(c.sourcePaths, c.outFile, func() {
|
paths := append(c.sourcePaths, c.modulePaths...)
|
||||||
|
watchFilesystem(paths, c.outFile, func() {
|
||||||
log.Printf("BUILDING: %s", buildName)
|
log.Printf("BUILDING: %s", buildName)
|
||||||
buildOutput(c)
|
buildOutput(c)
|
||||||
})
|
})
|
||||||
|
|
1
usage.go
1
usage.go
|
@ -48,6 +48,7 @@ Options:
|
||||||
-m SRC, --module=SRC Module sources (repeatable); may consist of supported
|
-m SRC, --module=SRC Module sources (repeatable); may consist of supported
|
||||||
files and/or directories to recursively search for
|
files and/or directories to recursively search for
|
||||||
such files.
|
such files.
|
||||||
|
--no-trim Do not trim whitespace surrounding passages.
|
||||||
-o FILE, --output=FILE Name of the output file (default: %q).
|
-o FILE, --output=FILE Name of the output file (default: %q).
|
||||||
-s NAME, --start=NAME Name of the starting passage (default: the passage
|
-s NAME, --start=NAME Name of the starting passage (default: the passage
|
||||||
set by the story data, elsewise %q).
|
set by the story data, elsewise %q).
|
||||||
|
|
|
@ -23,7 +23,7 @@ var (
|
||||||
// tweegoVersion holds the current version info.
|
// tweegoVersion holds the current version info.
|
||||||
tweegoVersion = versionInfo{
|
tweegoVersion = versionInfo{
|
||||||
major: 2,
|
major: 2,
|
||||||
minor: 0,
|
minor: 1,
|
||||||
patch: 0,
|
patch: 0,
|
||||||
pre: "",
|
pre: "",
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue