Writing Twee code with Naracea

Recently I’ve written short interactive story (in Czech, of course) using twee (while I like idea of Twine’s user interface, I’m text/code person, so I prefer to use twee). I used my own text editor Naracea, because, obviously, it fits all of my needs, and it integrates with twee quite nicely (as I will show below).

Naracea supports branching, which can be used either to branch text to different versions inside of single file, or it can base multiple branches on initial state of the first branch and thus create multiple “notebooks” inside of one document. When writing story, I was experimenting with this for a while, and after couple of passages I realized, that keeping everything in single branch (or single text file), leads to horrible mess which I’m not able to manage.

So I’ve split whole story to multiple branches (around sixty when it was done) with most branches having only one passage in it.

That was nice and all, but there were two problems. One is necessity to setup each branch manually when it was created. I didn’t foresee this as a problem when I was using the editor for usual things like writing blog posts etc., but here it quickly became a major pain point. Fortunately all settings in Naracea are hierarchical, so when branch doesn’t define e.g. spell checker language, it will look in document’s settings (and then in global application settings).

Recently I added plugin model and possibility to script Naracea with IronPython, so it was quite easy to write little script to handle the problem:

import clr
# Get some types we need to use
clr.AddReference("Common")
clr.AddReference("Core")
from Naracea.Common import ( Settings )
from Naracea.Core.Spellcheck import ( DictionaryDescriptor )
from System import ( Exception )  

# Check whether a document is open and active.
document = PluginHost.Application.ActiveDocument  
if document == None:
	raise Exception("No document")

# Get spellcheck language from current branch. This way we don't need to import any further types.
# Just set spelling language on one branch from app UI.
currentSpellingLanguage = document.ActiveBranch.Settings.Get[DictionaryDescriptor](Settings.SpellcheckLanguage)
print currentSpellingLanguage.DictionaryName

# Now apply settings on document level.
# Since settings are hierarchical, when nothing is set on branch, document value is used.
# We set spellchecker, export format + extension and syntax highlighter.
document.Settings.Set(Settings.SpellcheckLanguage, currentSpellingLanguage)
document.Settings.Set(Settings.BranchAutoExportFormat, "TXT")
document.Settings.Set(Settings.BranchAutoExportExtension, ".tw")
document.Settings.Set(Settings.ViewTextEditorSyntaxHighlighting, "Twee")

# If there are existing branches in document, setup their exporting.
for branch in document.Branches:
	branch.Settings.Set(Settings.BranchAutoExportFormat, "TXT");
	branch.Settings.Set(Settings.BranchAutoExportExtension, ".tw");
	branch.Editor.Settings.Set(Settings.ViewTextEditorSyntaxHighlighting, "Twee");

Now I just setup one branch and run the script propagate settings to document (and all existing branches). (Also remember to close and open document again to see the changes, because I wasn’t planning things like this when I was writing those parts of Naracea — I’ll fix this in some future release.)

Nice.

The second problem was, that when all source text was kept in single branch, I was able to setup autoexport for a branch and get whole twee file (and then run the twee from command line to process it into HTML), now I have tens of branches which need to be merged.

So I created another script to fix this issue:

# CLR is needed
import clr   
# Some references to assemblies
clr.AddReference("Plugin")
clr.AddReference("System.Threading")

# Import classes we are going to use
from System.Threading import ( AutoResetEvent )
from System.Diagnostics import ( Process, ProcessStartInfo )   
from System.IO import ( Path )   
from System import ( Exception )    
from Naracea.Plugin.Messages import ( BranchShiftingFinished )

# No document? Report error
if PluginHost.Application.ActiveDocument == None:  
     raise Exception("No document open.")

# Here will be the merged story text
mergedText = ""

# We need to iteratre over all branches, get their text and add it to the mergedText variable.
# However some branches might not be activated yet (Naracea lazy loads branches to save 
# memory and improve file opening times), so we need to ensure they are all active before 
# getting text out of them.
document = PluginHost.Application.ActiveDocument  
activeBranch = document.ActiveBranch
# We will sync on this event
waitEvent = AutoResetEvent(False)
# Subscribe for message signalling branch was successfully initialized
PluginHost.Bus.Subscribe[BranchShiftingFinished](lambda msg: waitEvent.Set());
# Iterate all branches
for branch in document.Branches:
	if not branch.IsActivated:
		# Branch is not active, we need to activate it by setting it as active branch
		document.ActiveBranch = branch
		# And now we need to wait till branch is initialized
		waitEvent.WaitOne();
	
	# It is safe to get text now
	mergedText += branch.Editor.Text + "\n\n"

# Restore last active branch
document.ActiveBranch = activeBranch

# Log merged text to console if you need it
# print mergedText

# Now we prepare save path
# File might not be saved yet, and we will use temp path in such case
sourcePath = PluginHost.Application.ActiveDocument.Filename  
if sourcePath != None:  
     sourcePath = Path.GetDirectoryName(sourcePath)       
else:  
     sourcePath = Path.GetTempPath()  
       
# Exported file name building happens here
branchExportFilename = "tweenExport.tw"  
sourceFile = Path.Combine(sourcePath, branchExportFilename)
print sourceFile

# Open file and save text as UTF8
outFile = open(sourceFile, "w")
outFile.write(mergedText.encode('utf8'))
outFile.close()

# Now generate target filename 
targetFile = Path.Combine(sourcePath, Path.GetFileNameWithoutExtension(sourceFile)) + ".html"   
print targetFile   
  
# Following calls twee and executes conversion
compiler = Process()   
compiler.StartInfo.FileName = "cmd"   
compiler.StartInfo.WorkingDirectory = sourcePath   
compiler.StartInfo.Arguments = "/c c:\\devtools\\python27\\python.exe c:\\devtools\\twee\\twee -t sugarcane \"{0}\" > \"{1}\"".format(sourceFile, targetFile)   
compiler.StartInfo.UseShellExecute = False   
compiler.StartInfo.CreateNoWindow = True  
compiler.StartInfo.RedirectStandardOutput = True 
compiler.Start()   
output = compiler.StandardOutput.ReadToEnd()   
compiler.WaitForExit()   
print output 

# Open converted HTML file in default browser
Process.Start(ProcessStartInfo(targetFile))   

This will merge all branches in one text, save it to disk, call twee to generate HTML and open exported file in default web browser.

I guess this makes Naracea nice alternative for developing Twine/twee stories on Windows:

Twee code