gamebookformat/output.py

235 lines
9.0 KiB
Python

import os
import os.path
import sys
# Hardcoded list of tags allowed without -x flag.
BUILTIN_TAGS = ['add',
'atleast',
'b',
'collect',
'cost',
'count',
'dec',
'drop',
'found',
'has',
'hasnot',
'img',
'inc',
'init',
'lessthan',
'min',
'morethan',
'random',
'set',
'take',
'trade',
'xor']
NOT_BUILTIN_TAG_MESSAGE = "Unknown tag %s not allowed without -x flag."
COUNTER_CREATE_TAG = 'count'
COUNTER_USE_TAGS = set(['set', 'inc', 'dec', 'min',
'lessthan', 'morethan', 'atleast'])
class OutputFormat (object):
"Handles book output. Big FIXME required to make sense."
def __init__(self, templates, allow_unknown_tags, quote, name):
self.templates = templates
self.allow_unknown_tags = allow_unknown_tags
self.format_quote = quote
self.name = name
self.counter_names = {}
def quote(self, s):
if s:
return self.format_quote(s)
else:
return ""
def format_begin(self, bookconfig):
# FIXME make sure book config is properly quoted
return self.format_with_template("begin", bookconfig)
def format_intro_sections(self, introsections, shuffled_sections):
res = ""
for s in introsections:
if not s.hastag('dummy'):
res += self.format_intro_section(s, shuffled_sections)
return res
def format_intro_section(self, section, shuffled_sections):
# FIXME some serious code-duplication here
refs = []
refsdict = ReferenceFormatter(section.nr,
shuffled_sections.name_to_nr,
shuffled_sections.missingto,
self.templates.get("section_ref"),
self.templates.get("named_section_ref"),
self.quote)
formatted_text = self.format_section_body(section, refsdict)
return self.format_with_template("introsection", {
'name' : section.name,
'text' : formatted_text
})
def format_sections_begin(self, bookconfig):
return self.format_with_template("sections_begin",
bookconfig)
def format_shuffled_sections(self, shuffled_sections):
res = ""
for i, p in enumerate(shuffled_sections.as_list):
if p and not p.hastag('dummy'):
res += self.format_section(p, shuffled_sections)
elif i > 0:
res += self.format_empty_section(i)
return res
def format_section(self, section, shuffled_sections):
refs = []
refsdict = ReferenceFormatter(section.nr,
shuffled_sections.name_to_nr,
shuffled_sections.missingto,
self.templates.get("section_ref"),
self.templates.get("named_section_ref"),
self.quote)
formatted_text = self.format_section_body(section, refsdict)
return self.format_with_template("section", {
'nr' : section.nr,
'name' : section.name,
'text' : formatted_text
})
def format_section_body(self, section, references):
i = 0
res = ""
# FIXME refactor for readability once good tests are in place
while i < len(section.text):
ref_start = section.text.find('[[', i)
tag_start = section.text.find('[', i)
ref_name = None
if ref_start >= 0 and ref_start <= tag_start:
res += self.format_text(section.text[i:ref_start])
ref_end = section.text.find(']]', ref_start)
if ref_end > ref_start:
ref_name_div_start = section.text.find('][',
ref_start, ref_end)
if ref_name_div_start > ref_start:
ref_name = section.text[ref_name_div_start+2:ref_end]
ref = section.text[ref_start+2:ref_name_div_start]
else:
ref = section.text[ref_start+2:ref_end]
splitref = ref.split()
if len(splitref) > 1:
for refmod in splitref[:-1]:
res += self.format_with_template(refmod,
references)
res += references.ref(splitref[-1], ref_name)
i = ref_end + 2
else:
raise Exception('Mismatched ref start [[ in section %s' %
self.name)
elif tag_start >= 0:
res += self.format_text(section.text[i:tag_start])
tag_end = section.text.find(']', tag_start)
if tag_end < 0:
raise Exception('Mismatched tag start [ in section %s' %
self.name)
tag = section.text[tag_start+1:tag_end].strip()
tagparts = tag.split()
tagname = tagparts[0]
if not self.allow_unknown_tags and tagname not in BUILTIN_TAGS:
raise Exception(NOT_BUILTIN_TAG_MESSAGE % tagname)
end_tag_start = section.text.find('[', tag_end)
if (not end_tag_start > tag_end
and section.text[end_tag_start].startswith('[/' + tagname
+ ']')):
raise Exception('Bad format %s tag in %s.' % (
tag, self.name))
inner = section.text[tag_end+1:end_tag_start]
# FIXME this pollutes the mutable references object
references['inner'] = self.quote(inner)
for i, arg in enumerate(tagparts[1:]):
references['arg%d' % (i+1)] = self.quote(arg)
if tagname == COUNTER_CREATE_TAG and len(tagparts) > 1:
self.counter_names[tagparts[1]] = self.quote(inner)
references['counter'] = self.quote(inner)
elif tagname in COUNTER_USE_TAGS and len(tagparts) > 1:
if tagparts[1] in self.counter_names:
references['counter'] = self.counter_names[tagparts[1]]
f = self.format_with_template(tagname, references)
if len(f) > 0:
res += f
else:
res += self.quote(inner)
i = section.text.find(']', end_tag_start) + 1
else:
res += self.format_text(section.text[i:])
break
return res
def format_text(self, text):
return self.format_with_template('text', {'text' : self.quote(text)})
def format_empty_section(self, nr):
return self.format_with_template("empty_section", {
'nr' : nr,
})
def format_end(self, bookconfig):
return self.format_with_template("end", bookconfig)
def format_with_template(self, name, values=None):
template = self.templates.get(name)
if values:
return template % values
else:
return template
class ReferenceFormatter (object):
"There is probably a better way, but this hack seems to work."
def __init__(self, from_nr, name_to_nr, missingto, ref_template,
named_ref_template, quote):
self.from_nr = from_nr
self.name_to_nr = name_to_nr
self.ref_template = ref_template
self.named_ref_template = named_ref_template
self.items = {'nr' : from_nr}
self.quote = quote
self.missingto = missingto
def get_to_nr(self, key):
if key in self.name_to_nr:
return self.name_to_nr[key]
elif self.missingto in self.name_to_nr:
return self.name_to_nr[self.missingto]
else:
raise Exception('Missing reference target: %s' % key)
def __getitem__(self, key):
if key in self.items:
return self.quote(self.items[key])
else:
return self.ref_template % {
'nr' : self.get_to_nr(key),
'from_nr' : self.from_nr
}
def ref(self, key, name):
values = {
'nr' : self.get_to_nr(key),
'from_nr' : self.from_nr,
'name' : self.quote(name)
}
if name:
return self.named_ref_template % values
else:
return self.ref_template % values
def __setitem__(self, key, value):
self.items[key] = value
def __delitem__(self, key):
del self.items[key]