1
0
Fork 0
mirror of https://bitbucket.org/oreolek/imaginary-realities.git synced 2024-05-15 23:48:27 +03:00
imaginary-realities/gensite.py
2015-06-04 17:29:02 +12:00

567 lines
22 KiB
Python

# CURRENT:
# TODO: Shared
# TODO: generate TARGET_ISSUES
# TODO: Is this target necessary?
# TODO: Currently supporting css/js/... comes from website, will need to contemplate custom versions.
# TODO: The issue_nav is website specific. tp.issue_nav will be None if it should be excluded.
# TODO: generate TARGET_EBOOK
# TODO: "templates/ebook/base.html" needs to be modified to support simple article formatting.
# TODO: Send HTML & CSS to ebook format converter:
# TODO: epub generation
# TODO: pdf generation
# TODO: generate TARGET_WEBSITE
# TODO: Top menu needs to be adjusted between website level, and issue level.
# TODO: Add disqus section to all issue article pages, maybe some selected other pages (introduction?)
# TODO: Add disqus section to home page.
# TODO: Add disqus section to all website pages?
# TODO: Detect articles with featured status data, and randomly pick from them.
# TODO: Analytics should be added.
# TODO: RSS feed needs to be generated automatically.
import calendar
import codecs
import datetime
import email.utils
import json
import os
import shutil
import sys
import time
import jinja2
import feedformatter
FLAG_ONLINE = 1 << 31
TARGET_EBOOK = 1
TARGET_WEBSITE = 2
TARGET_ISSUES = 4
# This defines where to look for the base template for a given target.
templates_by_target = {
TARGET_EBOOK: os.path.join("templates", "ebook"),
TARGET_WEBSITE | FLAG_ONLINE: os.path.join("templates", "website"),
TARGET_WEBSITE: os.path.join("templates", "website"),
TARGET_ISSUES: os.path.join("templates", "website"),
}
setting_website_hidden_issue_pages = [ "copyright", "request-for-content" ]
setting_finalise = False
setting_generation_target = TARGET_WEBSITE # | FLAG_ONLINE
if setting_generation_target not in templates_by_target:
print >> sys.stderr, "Unknown target:", setting_generation_target
sys.exit(1)
setting_base_template = templates_by_target[setting_generation_target]
if setting_generation_target == TARGET_WEBSITE | FLAG_ONLINE:
setting_use_minimised_files = True
else:
setting_use_minimised_files = False
PAGE_OTHER = 1
PAGE_ARTICLE = 2
issue_data = {
# (volume_number, issue_number, (year, month))
(5,1,(2013,12)): [
(PAGE_OTHER, "introduction"),
(PAGE_OTHER, "copyright"),
(PAGE_ARTICLE, "modern-interface-modern-mud"),
(PAGE_ARTICLE, "well-built-zone-work-art"),
(PAGE_ARTICLE, "journey-through-paradice"),
(PAGE_ARTICLE, "blind-accessibility-challenges-opportunities"),
(PAGE_ARTICLE, "evennia-introduction"),
(PAGE_ARTICLE, "getting-roleplaying-scene-going"),
(PAGE_ARTICLE, "introducing-new-players-redesigning-mud-school"),
(PAGE_ARTICLE, "hunger-game-learned-break-ship-bottle"),
(PAGE_ARTICLE, "help-save-old-mudding-resources"),
(PAGE_OTHER, "request-for-content"),
(PAGE_OTHER, "staff"),
],
(6,1,(2014,4)): [
(PAGE_OTHER, "introduction"),
(PAGE_OTHER, "copyright"),
(PAGE_ARTICLE, "a-journey-through-paradice-ii"),
(PAGE_ARTICLE, "building-a-mech-in-evennia"),
(PAGE_ARTICLE, "describing-a-virtual-world"),
(PAGE_ARTICLE, "dynamic-room-descriptions"),
(PAGE_ARTICLE, "saddle-up"),
(PAGE_ARTICLE, "the-successful-quest-builder"),
(PAGE_ARTICLE, "your-mud-should-have-an-account-system"),
(PAGE_ARTICLE, "help-save-old-mudding-resources"),
(PAGE_OTHER, "request-for-content"),
(PAGE_OTHER, "staff"),
],
(7,1,(2015,1)): [
(PAGE_OTHER, "introduction"),
(PAGE_OTHER, "copyright"),
(PAGE_ARTICLE, "choosing-an-emoting-system"),
(PAGE_ARTICLE, "dungeon-keeper"),
(PAGE_ARTICLE, "what-i-do-now"),
(PAGE_ARTICLE, "worlds-in-which-we-wander"),
(PAGE_OTHER, "staff"),
],
(7,2,(2015,4)): [
(PAGE_OTHER, "introduction"),
(PAGE_OTHER, "copyright"),
(PAGE_ARTICLE, "bartering"),
(PAGE_ARTICLE, "is-structuralism-a-viable-critical-lens-for-roguelike-games"),
(PAGE_OTHER, "request-for-content"),
(PAGE_OTHER, "staff"),
],
}
class TemplateParameters(object):
pass
jinja2_env = jinja2.Environment(loader=jinja2.FileSystemLoader([ 'templates', setting_base_template ]))
def init_template_data(tp):
tp.sections = TemplateParameters()
if not hasattr(tp, "link_prefix"):
tp.link_prefix = ""
tp.homepage_link = tp.link_prefix +"index.html"
tp.contact_link = tp.link_prefix +"contact.html"
tp.copyright_link = tp.link_prefix +"copyright.html"
tp.contribute_link = tp.link_prefix +"contribute.html"
tp.links_link = tp.link_prefix +"links.html"
tp.rss_link = "http://journal.imaginary-realities.com/feed_rss2.xml"
tp.twitter_link = "https://twitter.com/irjrnl"
tp.reddit_link = "http://www.reddit.com/r/imaginaryrealities/"
if (setting_generation_target & FLAG_ONLINE) == FLAG_ONLINE:
tp.js_jquery_link = "http://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"
else:
if setting_use_minimised_files:
tp.js_jquery_link = tp.link_prefix +"js/jquery-1.11.3.min.js"
else:
tp.js_jquery_link = tp.link_prefix +"js/jquery-1.11.3.js"
tp.license_text = "CC BY SA NC"
tp.license_link = "http://creativecommons.org/licenses/by-nc-sa/3.0/nz/"
tp.website_title_text = "Imaginary Realities"
tp.menu = TemplateParameters()
tp.menu.back_issues_title = "Back Issues"
tp.menu.latest_issue_title = "Current Issue"
get_latest_issue_data(tp)
get_back_issues_data(tp)
def get_latest_issue_data(tp):
tp.sections.latest_issue = TemplateParameters()
tp.sections.latest_issue.title = "Latest Issue"
tp.sections.latest_issue.issue_articles = []
issue_keys = issue_data.keys()
issue_keys.sort(lambda a, b: cmp(b, a))
issue_key = issue_keys[0]
volume_number, issue_number, (year_number, month_number) = issue_key
issue_link = "volume-%02d/issue-%02d/" % (volume_number, issue_number)
tp.sections.latest_issue.issue_title = "Volume %d, Issue %d (%s %d)" % (volume_number, issue_number, calendar.month_name[month_number], year_number)
for article_type, page_dirname in issue_data[issue_key]:
if page_dirname in setting_website_hidden_issue_pages: continue
article = TemplateParameters()
article.title = get_article_block_content(volume_number, issue_number, page_dirname, "page_title")
article.link = issue_link + page_dirname +"/index.html"
tp.sections.latest_issue.issue_articles.append(article)
tp.sections.latest_issue.read_text = "Read this issue"
tp.sections.latest_issue.read_link = issue_link +"index.html"
def get_back_issues_data(tp):
tp.sections.back_issues = TemplateParameters()
tp.sections.back_issues.title = "Back Issues"
tp.sections.back_issues.issues = []
issue_keys = issue_data.keys()
issue_keys.sort(lambda a, b: cmp(b, a))
for issue_key in issue_keys[1:]:
volume_number, issue_number, (year_number, month_number) = issue_key
issue = TemplateParameters()
issue.link = "volume-%02d/issue-%02d/index.html" % (volume_number, issue_number)
issue.volume_text = "Volume %d, Issue %d" % (volume_number, issue_number)
issue.publication_year_text = "%s %d" % (calendar.month_name[month_number], year_number)
tp.sections.back_issues.issues.append(issue)
def generate_website_index_page():
t = jinja2_env.get_template("homepage.html")
output_path = os.path.join("website", "index.html")
tp = TemplateParameters()
init_template_data(tp)
tp.sections.introduction = TemplateParameters()
tp.sections.featured_article = TemplateParameters()
tp.sections.recent_comments = TemplateParameters()
# SECTION: Introduction
tp.sections.introduction.logo_text = "An old logo for Imaginary Realities, with a lower-case i beside a upper-case R"
tp.sections.introduction.logo_link = "images/logo.png"
# SECTION: Featured article
tp.sections.featured_article.title = "Featured Article"
featured_article_key = (7,1,"what-i-do-now")
volume_number, issue_number, page_dirname = featured_article_key
article_title = get_article_block_content(volume_number, issue_number, page_dirname, "page_title")
article_link = "volume-%02d/issue-%02d/%s/index.html" % (volume_number, issue_number, page_dirname)
article_authors = get_article_block_content(volume_number, issue_number, page_dirname, "article_authors")
feature_excerpt = get_article_block_content(volume_number, issue_number, page_dirname, "feature_excerpt")
feature_hype = get_article_block_content(volume_number, issue_number, page_dirname, "feature_hype")
tp.sections.featured_article.article_title = article_title
tp.sections.featured_article.article_hype_text = feature_hype
tp.sections.featured_article.article_quote_text = feature_excerpt
tp.sections.featured_article.article_authorname_text = article_authors
tp.sections.featured_article.read_text = "Read this article"
tp.sections.featured_article.read_link = article_link
# SECTION: Recent comments
tp.sections.recent_comments.title = "Recent Comments"
tp.sections.recent_comments.content = """<div class="watch-this-space">Disqus comments on articles will be populated here dynamically at some later point. Watch this space!</div>"""
html = t.render(tp=tp)
with codecs.open(output_path, "wb", "utf-8") as f:
f.write(html)
def generate_website_contact_page():
t = jinja2_env.get_template("contact.html")
output_path = os.path.join("website", "contact.html")
tp = TemplateParameters()
init_template_data(tp)
html = t.render(tp=tp)
with codecs.open(output_path, "wb", "utf-8") as f:
f.write(html)
def generate_website_links_page():
t = jinja2_env.get_template("links.html")
output_path = os.path.join("website", "links.html")
tp = TemplateParameters()
init_template_data(tp)
html = t.render(tp=tp)
with codecs.open(output_path, "wb", "utf-8") as f:
f.write(html)
def generate_website_copyright_page():
t = jinja2_env.get_template("copyright.html")
output_path = os.path.join("website", "copyright.html")
tp = TemplateParameters()
init_template_data(tp)
html = t.render(tp=tp)
with codecs.open(output_path, "wb", "utf-8") as f:
f.write(html)
def generate_website_contribute_page():
t = jinja2_env.get_template("contribute.html")
output_path = os.path.join("website", "contribute.html")
tp = TemplateParameters()
init_template_data(tp)
html = t.render(tp=tp)
with codecs.open(output_path, "wb", "utf-8") as f:
f.write(html)
def generate_website_rss():
feed = feedformatter.Feed()
feed.feed["title"] = "Imaginary Realities"
feed.feed["link"] = "http://journal.imaginary-realities.com/"
feed.feed["author"] = "Richard Tew"
feed.feed["description"] = "Announcements related to the Imaginary Realities publication"
# Load in existing items, in order of oldest to newest.
feed_items = json.load(open("feed.json", "rb"))
existing_links = []
for feed_item in feed_items:
if type(feed_item["pubDate"]) in (str, unicode): # RFC2822
d = email.utils.parsedate_tz(feed_item["pubDate"])
feed_item["pubDate"] = email.utils.mktime_tz(d) # Seconds since epoch.
feed.items.append(feed_item)
link = feed_item["link"]
if link.endswith("/"):
link += "index.html"
existing_links.append(link)
# Add missing issues in order of publication (which is how the feed goes).
issue_keys = issue_data.keys()
issue_keys.sort()
for issue_key in issue_keys:
volume_number, issue_number, (year_number, month_number) = issue_key
missing_link = feed.feed["link"] + "volume-%02d/issue-%02d/index.html" % (volume_number, issue_number)
if missing_link not in existing_links:
print "RSS: Adding issue to feed, volume %d, issue %d" % (volume_number, issue_number)
item = {}
item["title"] = "Imaginary Realities - Volume %d, Issue %d" % (volume_number, issue_number)
item["description"] = "Our latest issue as of %s %d. Long delayed, but finally here. Read it online, or download it as a zip archive, epub e-book or PDF document. Follow our twitter, if that's your thing. Articles related to mudding, and other text-based forms of gaming." % (calendar.month_name[month_number], year_number)
item["link"] = missing_link
item["pubDate"] = time.time()
item["guid"] = "%d" % (time.time() * 100)
feed_items.append(item)
feed.items.append(item)
if setting_finalise:
# Update the json record of what has already been published (to try and avoid sending all entries out again, everytime we add a new RSS entry.
json.dump(feed_items, open("feed.json", "wb"))
# Generate the RSS2 feed.
feed.format_rss2_file(os.path.join("website", "feed_rss2.xml"))
### ISSUE GENERATION
def generate_website():
print "Generating website"
generate_website_index_page()
generate_website_contact_page()
generate_website_copyright_page()
generate_website_contribute_page()
generate_website_links_page()
generate_website_rss()
if (setting_generation_target & TARGET_WEBSITE) == TARGET_WEBSITE:
for dirname in ("css", "js", "fonts", "images"):
shutil.copytree(os.path.join("templates", "website", dirname), os.path.join("website", dirname))
def generate_issue_page(issue_nav, volume_number, issue_number, year_number, month_number, page_type, page_dirname):
outputdir_path = os.path.join("website", "volume-%02d" % volume_number, "issue-%02d" % issue_number, page_dirname)
inputdir_path = os.path.join("templates", "volume%02d_issue%02d" % (volume_number, issue_number), page_dirname)
inputtemplate_name = "index.html"
shutil.copytree(inputdir_path, outputdir_path, ignore=shutil.ignore_patterns(inputtemplate_name))
jinja2_env = jinja2.Environment(loader=jinja2.FileSystemLoader([ "templates", setting_base_template, inputdir_path ]))
t = jinja2_env.get_template(inputtemplate_name)
output_path = os.path.join(outputdir_path, "index.html")
tp = TemplateParameters()
# TODO: Path should be set more intelligently. Perhaps absolute in some cases.
if (setting_generation_target & TARGET_WEBSITE) == TARGET_WEBSITE:
# Full scope is [TOP/VOLUME/ISSUE/PAGE.html], so 3 levels moves from PAGE to TOP.
tp.link_prefix = "../../../"
else:
# Full scope is [ISSUE/PAGE.html], so 1 levels moves from PAGE to ISSUE.
tp.link_prefix = "../"
init_template_data(tp)
tp.volume_number = volume_number
tp.issue_number = issue_number
tp.issue_link = "../index.html"
tp.issue_nav = issue_nav
html = t.render(tp=tp)
with codecs.open(output_path, "wb", "utf-8") as f:
f.write(html)
def generate_issue_toc(issue_nav, issue_metadata, issue_pagedata):
volume_number, issue_number, (year_number, month_number) = issue_metadata
inputdir_path = os.path.join("templates", "issue")
outputdir_path = os.path.join("website", "volume-%02d" % volume_number, "issue-%02d" % issue_number)
jinja2_env = jinja2.Environment(loader=jinja2.FileSystemLoader([ "templates", setting_base_template, inputdir_path ]))
t = jinja2_env.get_template("toc.html")
output_path = os.path.join(outputdir_path, "index.html")
tp = TemplateParameters()
# TODO: Path should be set more intelligently. Perhaps absolute in some cases.
if (setting_generation_target & TARGET_WEBSITE) == TARGET_WEBSITE:
# Full scope is [TOP/VOLUME/ISSUE/<HERE>/PAGE], so 2 levels moves from PAGE to TOP.
tp.link_prefix = "../../"
else:
# Full scope is [<HERE>/PAGE], so 0 levels moves to ISSUE.
tp.link_prefix = ""
init_template_data(tp)
tp.volume_number = volume_number
tp.issue_number = issue_number
tp.issue_link = "index.html"
tp.logo_text = "An old logo for Imaginary Realities, with a lower-case i beside a upper-case R"
tp.logo_link = "images/logo.png"
tp.page_title = "Table of Contents"
tp.issue = TemplateParameters()
tp.issue.articles = []
for page_type, page_dirname in issue_pagedata:
if page_dirname in setting_website_hidden_issue_pages: continue
article = TemplateParameters()
article.title = get_article_block_content(volume_number, issue_number, page_dirname, "page_title")
article.author_names = get_article_block_content(volume_number, issue_number, page_dirname, "article_authors").replace(" and ", "<br/>")
article.link = page_dirname +"/index.html"
tp.issue.articles.append(article)
tp.issue.read_link = issue_pagedata[0][1] +"/index.html"
tp.issue.read_text = "Read on.."
tp.issue_nav = issue_nav
html = t.render(tp=tp)
with codecs.open(output_path, "wb", "utf-8") as f:
f.write(html)
def generate_issues(issue_data):
issue_metadatas = issue_data.keys()
issue_metadatas.sort()
last_issue_index = len(issue_metadatas)-1
for issue_index, issue_metadata in enumerate(issue_metadatas):
issue_pagedata = issue_data[issue_metadata]
volume_number, issue_number, (year_number, month_number) = issue_metadata
print "Generating volume %d, issue %d" % (volume_number, issue_number)
volume_path = os.path.join("website", "volume-%02d" % volume_number)
issue_path = os.path.join(volume_path, "issue-%02d" % issue_number)
os.makedirs(issue_path)
images_input_path = os.path.join("templates", "volume%02d_issue%02d" % (volume_number, issue_number), "images")
if os.path.exists(images_input_path):
images_output_path = os.path.join(issue_path, "images")
shutil.copytree(images_input_path, images_output_path)
# Copy a set of the dependent files to each issue, for local use.
if (setting_generation_target & TARGET_ISSUES) == TARGET_ISSUES:
parent_template_path = os.path.join("templates", "website")
for entry_name in os.listdir(parent_template_path):
entry_path = os.path.join(parent_template_path, entry_name)
if os.path.isdir(entry_path):
shutil.copytree(entry_path, os.path.join(issue_path, entry_name))
# TODO: The whole issue_nav debacle should be skipped for non-website builds.
# TODO: Non-website builds should not have issue_nav
if (setting_generation_target & TARGET_WEBSITE) == TARGET_WEBSITE:
issue_nav = TemplateParameters()
else:
issue_nav = None
if issue_nav:
# Inter-article navigation button priming (setup done on per-article basis below).
default_prev_article_html = issue_nav.prev_article_html = "&#x00AB; article"
issue_nav.prev_article_class = ""
default_next_article_html = issue_nav.next_article_html = "article &#x00BB;"
issue_nav.next_article_class = ""
# Inter-issue navigation button setup.
default_prev_issue_html = issue_nav.prev_issue_html = "&#x00AB; issue"
if issue_index == 0:
issue_nav.prev_issue_class = "unlinked-button"
prev_issue_link = ""
else:
prev_volume_number, prev_issue_number, discarded = issue_metadatas[issue_index-1]
prev_issue_link = "volume-%02d/issue-%02d/index.html" % (prev_volume_number, prev_issue_number)
issue_nav.prev_issue_html = "<a href='../../../%s'>%s</a>" % (prev_issue_link, default_prev_issue_html)
issue_nav.prev_issue_class = ""
default_next_issue_html = issue_nav.next_issue_html = "issue &#x00BB;"
if issue_index == last_issue_index:
issue_nav.next_issue_class = "unlinked-button"
next_issue_link = ""
else:
next_volume_number, next_issue_number, discarded = issue_metadatas[issue_index+1]
next_issue_link = "volume-%02d/issue-%02d/index.html" % (next_volume_number, next_issue_number)
issue_nav.next_issue_html = "<a href='../../../%s'>%s</a>" % (next_issue_link, default_next_issue_html)
issue_nav.next_issue_class = ""
last_page_link = "../index.html"
last_page_index = len(issue_pagedata)-1
for page_index, (page_type, page_dirname) in enumerate(issue_pagedata):
if issue_nav:
issue_nav.prev_article_class = "unlinked-button"
issue_nav.next_article_class = "unlinked-button"
issue_nav.prev_article_html = default_prev_article_html
issue_nav.next_article_html = default_next_article_html
set_prev = False
set_next = False
next_link = ""
# The article hotbar skips over the copyright page.
if page_dirname not in setting_website_hidden_issue_pages:
if page_index == last_page_index:
set_prev = True
else:
set_next = set_prev = True
if set_prev:
issue_nav.prev_article_html = "<a href='%s'>%s</a>" % (last_page_link, default_prev_article_html)
issue_nav.prev_article_class = ""
if set_next:
# The article hotbar skips over the copyright page.
j = page_index+1
while j < len(issue_pagedata) and issue_pagedata[j][1] in setting_website_hidden_issue_pages:
j += 1
if j < len(issue_pagedata):
next_link = "../"+ issue_pagedata[j][1] +"/index.html"
issue_nav.next_article_html = "<a href='%s'>%s</a>" % (next_link, default_next_article_html)
issue_nav.next_article_class = ""
generate_issue_page(issue_nav, volume_number, issue_number, year_number, month_number, page_type, page_dirname)
if issue_nav:
# The article hotbar skips over the copyright page.
if page_dirname not in setting_website_hidden_issue_pages:
last_page_link = "../"+ page_dirname +"/index.html"
if issue_nav:
if prev_issue_link:
issue_nav.prev_issue_html = "<a href='../../%s'>%s</a>" % (prev_issue_link, default_prev_issue_html)
issue_nav.prev_issue_class = ""
else:
issue_nav.prev_issue_html = default_prev_issue_html
issue_nav.prev_issue_class = "unlinked-button"
if next_issue_link:
issue_nav.next_issue_html = "<a href='../../%s'>%s</a>" % (next_issue_link, default_next_issue_html)
issue_nav.next_issue_class = ""
else:
issue_nav.next_issue_html = default_next_issue_html
issue_nav.next_issue_class = "unlinked-button"
issue_nav.prev_article_class = "unlinked-button"
issue_nav.next_article_class = ""
issue_nav.prev_article_html = default_prev_article_html
issue_nav.next_article_html = "<a href='%s'>%s</a>" % (issue_pagedata[0][1] +"/index.html", default_next_article_html)
generate_issue_toc(issue_nav, issue_metadata, issue_pagedata)
def get_article_block_content(volume_number, issue_number, page_dirname, block_name):
# Load the template.
jinja2_env = jinja2.Environment(loader=jinja2.FileSystemLoader([
"templates", setting_base_template,
os.path.join("templates", "volume%02d_issue%02d" % (volume_number, issue_number), page_dirname)
]))
t = jinja2_env.get_template("index.html")
if block_name in t.blocks:
return next(t.blocks[block_name](None)).strip()
return ""
if __name__ == "__main__":
if (setting_generation_target & TARGET_WEBSITE) == TARGET_WEBSITE:
if os.path.exists("website"):
shutil.rmtree("website")
generate_issues(issue_data)
generate_website()
if not setting_finalise:
print "WARNING: Results are not finalised, published result should be finalised."
# EOF