git @ Cat's Eye Technologies MARYSUE / master src / marysue / publisher.py
master

Tree @master (Download .tar.gz)

publisher.py @masterraw · history · blame

"""High-level orchestration of the writing of the novel."""

import os
import sys
from tempfile import mkstemp

import marysue.util as random
from marysue.util import capitalize, log
from marysue.editor import edit_story
from marysue.proofreader import proofread, word_count


class Novel(object):
    front_matter = """\
# A Time for Destiny

### The Illustrious Career of Serenity Starlight Warhammer O'James during her First Three Years in the Space Fighters

_SPACE SURGEON GENERALS WARNING:_ THE SPACE SURGEON GENERAL HAS DETERMINED THAT
EXPOSURE TO LARGE AMOUNTS OF COMPUTER-GENERATED FICTION MAY CAUSE HEADACHES,
DIZZINESS, NAUSEA, AND AN ACUTE DESIRE TO SKIP AROUND TO FIND THE GOOD BITS.

FOR YOUR OWN WELL-BEING, DO NOT EXCEED THE RECOMMENDED MAXIMUM INTAKE OF 2 (TWO)
CHAPTERS IN ANY 48 (FORTY EIGHT) HOUR PERIOD.

"""

    def __init__(self, chapters, generate_front_matter=True, synopsis=False, dump=False):
        self.chapters = chapters
        self.generate_front_matter = generate_front_matter
        self.synopsis = synopsis
        self.dump = dump

        self.text = ''
        self.used_titles = set()
        self.introduced = set()

    def suggest_title_objects(self, plot, objects=None):
        from marysue.plot import Kidnapping, LoseItem, Vanquished

        if objects is None:
            objects = set()
        if isinstance(plot, Kidnapping):
            objects.add(plot.subject)
        if isinstance(plot, (LoseItem, Vanquished)):
            objects.add(plot.object)
        for child in plot:
            self.suggest_title_objects(child, objects=objects)    
        return objects

    def pick_title(self, plot):
        titles = [o.definite for o in self.suggest_title_objects(plot)]
        if not titles:
            titles = ['The Destiny of Fate']
        base_title = capitalize(random.choice(tuple(titles)))
        prefixes = [
            'The Return of ',
            'The Revenge of ',
            'The Scourge of ',
            'The Menace of ',
            'The Secret of ',
            'The Time of ',
            'The Scourge of ',
            'The Mystery of ',
            'The Phantom of ',
        ]
        multipref = []
        for a in prefixes:
            for b in prefixes:
                multipref.append(a + b)
        prefixes.extend(multipref)
    
        title = base_title
        while title in self.used_titles:
            title = prefixes.pop(0) + base_title
        self.used_titles.add(title)

        return title

    def generate_chapter(self, n, plotter, **kwargs):
        ### tell the plotter to give us an acceptable plot ###

        plot = plotter.generate_acceptable_plot(**kwargs)

        ### dump the synopsis, if requested ###

        if self.synopsis:
            plot.print_synopsis()
            sys.exit(0)

        ### write a story around the plot, then edit it ###

        story = plotter.plot_to_story(plot)

        story = edit_story(story, self.introduced, **kwargs)

        ### dump the story, if requested ###

        if self.dump:
            story.dump(sys.stdout)
            sys.exit(0)

        ### give the thing a title that hasn't been used yet ###

        self.chapters[n]['commuted_plots'] = plotter.commuted_plots
        self.chapters[n]['plot'] = plot
        self.chapters[n]['title'] = self.pick_title(plot)

        ### install it in the chapters ###

        text = story.render()
        text = proofread(text)

        self.chapters[n]['text'] = text
        self.chapters[n]['word_count'] = word_count(text)

    def assemble_novel_text(self):
        self.text = ''

        # [WHARRGARBL](http://i1.kym-cdn.com/photos/images/newsfeed/000/032/388/wharrgarbl.jpg).

        if self.generate_front_matter:
            self.text += self.front_matter
            self.text += '\n\n## Contents\n\n'
            self.text += '<a name="contents"></a>\n\n'
            for n, chapter in enumerate(self.chapters):
                self.text += '1. [%s](#%s)  \n' % (chapter['title'], n)
            self.text += '\n\n'

        for n, chapter in enumerate(self.chapters):
            self.text += (
                ('<a name="%s"></a>\n\n' % n) +
                ('## %s. %s' % (n+1, chapter['title'])) +
                #('(%s)' % chapter['commuted_plots']) +
                '\n\n' +
                chapter['text'] +
                '\n\n'
            )
            #self.text += '[Up to Table of Contents](#contents)\n\n'

    def trim(self):
        ### trim the novel to reasonable length ###
        
        # Note: this isn't perfect, because every time we cut a chapter,
        # we make the table of contents shorter too.  But, it's usually OK.
        # The total number of words outside of any chapter is usually around 500.
        
        done = False
        while not done:
            self.assemble_novel_text()
            self.novel_wc = word_count(self.text)
            overrun = self.novel_wc - 50000
            could_be_cut = [(n, c) for (n, c) in enumerate(self.chapters) if c['word_count'] < overrun and c['position'] == 'middle']
            could_be_cut.sort(key=lambda pair: pair[1]['word_count'])
            if could_be_cut:
                n, chapter = could_be_cut[0]
                log('cutting:', n, chapter['title'], chapter['word_count'])
                self.chapters.remove(chapter)
            else:
                done = True

        self.total_wc = sum([chapter['word_count'] for chapter in self.chapters])
        self.avg_wc = (self.total_wc * 1.0) / (len(self.chapters) * 1.0)

        self.retitle_chapters()
        self.assemble_novel_text()
        self.novel_wc = word_count(self.text)

        for n, c in enumerate(self.chapters):
            if c['commuted_plots']:
                log(n+1, c['commuted_plots'])

    def retitle_chapters(self):
        self.used_titles = set()
        for n, c in enumerate(self.chapters):
            c['title'] = self.pick_title(c['plot'])

    def publish(self):
        fd, temp_filename = mkstemp(suffix='.md')
        os.close(fd)
        with open(temp_filename, 'w') as f:
            f.write(self.text)
            f.write("## word counts for this novel\n\n")
            for n, chapter in enumerate(self.chapters):
                f.write("Chapter %02d: %s  \n" % (n + 1, chapter['word_count']))
            f.write("Chapter total: %0.2d  \n" % self.total_wc)
            f.write("Average: %0.2d  \n" % self.avg_wc)
            f.write("Entire Novel: %s  \n" % self.novel_wc)
        log(temp_filename)
        html_filename = 'temp.html'
        output_filename = 'Serenity_Starlight.html'
        os.system("pandoc --from=markdown --to=html5 --css=marysue.css <%s >%s" % (temp_filename, html_filename))
        with open(html_filename, 'r') as f_in:
            with open(output_filename, 'w') as f_out:
                for line in f_in:
                    if 'rel="stylesheet"' in line:
                        f_out.write('<style type="text/css">\n')
                        f_out.write(open('marysue.css', 'r').read())
                        f_out.write('</style>\n')
                    else:
                        f_out.write(line)
        os.system("firefox %s &" % output_filename)