#!/usr/bin/env python3
import sys
import yaml
import yattag
TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type"
content="application/html; charset=utf-8"/>
<style type="text/css">
body {{ font-family: Fira Sans; }}
ul {{ list-style-type: '—'; margin-top: 2px; margin-bottom: 2px; }}
.main {{ width: 600px; margin-left: auto; margin-right: auto; }}
.key {{ color: #888; }}
.key:after {{ content: ": " }}
.val {{ font-weight: bold; }}
.section {{ padding-left: 30px; padding-right: 30px;
padding-top: 15px; padding-bottom: 15px;
margin: 20px; border: 1px solid; }}
.note {{ color: #888; }}
li {{ padding-left: 10px; margin-left: 5px; }}
.subhed {{ font-weight: bold; }}
</style>
</head>
<body><div class="main">{0}</div></body>
</html>
'''
def main(source):
'''Load a character YAML file and then HTMLify it'''
if source == '-':
char = yaml.load(sys.stdin)
else:
with open(source) as f:
char = yaml.load(f)
print(TEMPLATE.format(render(char)))
def stat_mod(n):
'''Get the modifier corresponding to a stat'''
mod = (n // 2) - 5
if mod < 0:
return str(mod)
else:
return '+' + str(mod)
STATS = [
'strength',
'dexterity',
'constitution',
'intelligence',
'wisdom',
'charisma',
'sanity'
]
def render(char):
'''
Turn a character into a nice HTML page. This uses a lot of
helper functions for common structures.
'''
doc, tag, text, line = yattag.Doc().ttl()
def field(name, source=char):
'''
Look up a field in `source` and print it nicely as a key/value
pair of some kind. This does a bit of inspection of the value,
and in the case of a list of dictionary, tries to do the right
thing and lay it out as an HTML unordered list.
'''
if isinstance(source[name], list):
line('span', name.title(), klass='subhed')
with tag('ul', id=name):
for elem in source[name]:
maybe_li(elem)
elif isinstance(source[name], dict):
line('span', name.title(), klass='subhed')
with tag('ul', id=name):
for k, elem in source[name].items():
maybe_li('{0}: {1}'.format(k, elem).capitalize())
else:
print_kv(name, source[name])
doc.stag('br')
def print_kv(name, val):
'''
Print a simple key/value pair nicely.
'''
with tag('span', id=name):
line('span', name.capitalize(), klass='key')
line('span', val, klass='val')
def stat_field(name, source=char['stats']):
'''
For fields representing a D&D stat, we also want to print the
modifier afterwards, for convenience.
'''
with tag('span', id=name):
line('span', name.title(), klass='key')
line('span', source[name], klass='val')
mod = stat_mod(source[name])
line('span', ' ({0})'.format(mod), klass='note')
doc.stag('br')
def maybe_li(val):
'''
Sometimes a list item is going to be just a string, but it
might have more structure than that, in which case we want
to print the tags on the structure, as well.
'''
if isinstance(val, str):
line('li', val.capitalize())
else:
print_li_with_note(val)
def print_li_with_note(val):
'''
Right now, I'm assuming all "rich" fields are dictionaries,
but this might not turn out to be true eventually. This
handles fields of the form `"item": {"note": "blah"}` by
formatting the note separately, and fields of the form
"foo": "bar" by printing them normally, because that lets
us pretend in the source text that we can just have single-line
fields with colons in them. (YAML doesn't interpret it like that!)
'''
for k, v in val.items():
with tag('li'):
text(k.capitalize())
if isinstance(v, dict):
for cl, tx in v.items():
doc.stag('br')
line('span', cl+': '+tx, klass='note')
else:
text(': ' + v)
# Now that we have that our of the way, everything else
# is just printing out the right pieces of data in order!
# basic character data
with tag('div', id='chardata', klass='section'):
field('name')
field('class')
field('level')
field('background')
field('race')
field('alignment')
# stat block
with tag('div', id='stats', klass='section'):
for stat in STATS:
stat_field(stat)
# saving throw proficiencies
with tag('div', id='saving_throws', klass='section'):
field('saving throws')
# combat stuff
with tag('div', id='combat', klass='section'):
field('ac')
field('speed')
field('hit point maximum')
field('hit die')
# spells and spell slots
with tag('div', id='spells', klass='section'):
field('spell slots')
field('spells')
# skill proficiencies
with tag('div', id='skills', klass='section'):
field('skills')
# equipment section
with tag('div', id='equipment', klass='section'):
field('equipment')
# personality section
with tag('div', id='personality', klass='section'):
field('personality')
field('ideals')
field('bonds')
field('flaw')
# features and traits
with tag('div', id='features_traits', klass='section'):
field('features & traits')
# other proficiencies
with tag('div', id='other_proficiencies', klass='section'):
field('languages')
field('proficiencies')
# and we're done!
return doc.getvalue()
if __name__ == '__main__':
main(sys.argv[1])