151 lines
4.8 KiB
Python
151 lines
4.8 KiB
Python
import argparse
|
|
from docxtpl import DocxTemplate, RichText
|
|
import markdown
|
|
from novel_stats.novel_stats import count_words
|
|
import tempfile
|
|
import MarkdownPP
|
|
import json
|
|
from lxml import etree
|
|
from importlib.resources import path
|
|
|
|
TITLE_MARKER = '# '
|
|
AUTHOR_MARKER = '### '
|
|
CHAPTER_MARKER = '## '
|
|
STATUS_MARKER = '[status]: # '
|
|
ACT_MARKER = '[act]: # '
|
|
COMMENT_MARKER = '[//]: # ' # Strandard markdown comment marker, supported by pandoc and calibre's ebook-convert
|
|
|
|
class Chapter:
|
|
def __init__(self, heading):
|
|
self.heading = heading
|
|
self.paragraphs = []
|
|
|
|
class ParseTarget:
|
|
TAGS = {'em':'italic', 'strong':'bold'}
|
|
def __init__(self):
|
|
self.cur = {key: False for key in self.TAGS}
|
|
self.par = RichText()
|
|
def start(self, tag, attrib):
|
|
if tag in self.TAGS:
|
|
self.cur[tag] = True
|
|
def end(self, tag):
|
|
if tag in self.TAGS:
|
|
self.cur[tag] = False
|
|
def data(self, data):
|
|
tags = {self.TAGS[tag]:self.cur[tag] for tag in self.TAGS}
|
|
self.par.add(data, **tags)
|
|
def close(self):
|
|
return self.par
|
|
|
|
def md_re_parser(md_paragraph, break_mark):
|
|
if md_paragraph == break_mark:
|
|
return None
|
|
html = markdown.markdown(md_paragraph)
|
|
target = ParseTarget()
|
|
parser = etree.XMLParser(target=target)
|
|
par = etree.XML(html, parser)
|
|
return par
|
|
|
|
|
|
def novel_parser(source_file, context = None):
|
|
if not context:
|
|
context = {'author_address': 'Street\nTown, State ZIP\nCountry',
|
|
'author_email': 'name@email.com',
|
|
'author_phone': 'PhoneNumber(s)',
|
|
'author_website': 'https://www.author.com',
|
|
'md_break_mark': '-*-',
|
|
'docx_break_mark': '#'}
|
|
context['chapters'] = []
|
|
|
|
wc = 0
|
|
chapter = Chapter('')
|
|
for line in source_file:
|
|
if line.startswith(TITLE_MARKER):
|
|
title = line[len(TITLE_MARKER):].strip('()\n')
|
|
context['project_title'] = title
|
|
wc += count_words(title)
|
|
elif line.startswith(AUTHOR_MARKER):
|
|
author_name = line[len(AUTHOR_MARKER):].strip('()\n')
|
|
if 'author_name' not in context:
|
|
context['author_name'] = author_name
|
|
context['penname'] = author_name
|
|
elif line.startswith(CHAPTER_MARKER):
|
|
if chapter.heading or chapter.paragraphs:
|
|
context['chapters'].append(chapter)
|
|
chapter = Chapter(line[len(CHAPTER_MARKER):].strip('()\n'))
|
|
wc += count_words(chapter.heading)
|
|
elif line.startswith(STATUS_MARKER) or line.startswith(ACT_MARKER) or line.startswith(COMMENT_MARKER):
|
|
pass
|
|
else:
|
|
stripped = line.strip()
|
|
if stripped:
|
|
wc += count_words(stripped)
|
|
chapter.paragraphs.append(md_re_parser(stripped, context['md_break_mark']))
|
|
context['chapters'].append(chapter)
|
|
|
|
source_file.close()
|
|
|
|
context['wc_1000'] = f'{(wc//1000)*1000:,}'
|
|
|
|
if 'header' not in context:
|
|
context['header'] = context['author_name'].split()[-1] + ' - ' + context['project_title'] + ' - '
|
|
|
|
return context
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
'markdown_file',
|
|
type=argparse.FileType('r'),
|
|
help='The markdown file for the novel, main file if a multi-file novel',
|
|
)
|
|
parser.add_argument(
|
|
'-pp',
|
|
action='store_true',
|
|
help='run markdown pre-processor, this allows for a multi-file input (e.g. each chapter in its own file), but requires the MarkdownPP python library',
|
|
)
|
|
parser.add_argument(
|
|
'--settings',
|
|
'-s',
|
|
type=argparse.FileType('r'),
|
|
help='setting json file',
|
|
)
|
|
parser.add_argument(
|
|
'--template',
|
|
'-t',
|
|
type=argparse.FileType('r'),
|
|
help='template docx file',
|
|
)
|
|
parser.add_argument(
|
|
'--output',
|
|
'-o',
|
|
type=argparse.FileType('w'),
|
|
help='output docx file',
|
|
)
|
|
arguments = parser.parse_args()
|
|
|
|
if arguments.template:
|
|
arguments.template.close()
|
|
doc = DocxTemplate(arguments.template.name)
|
|
else:
|
|
with path('novel_compiler.templates', 'novel.docx') as novel_template:
|
|
doc = DocxTemplate(novel_template)
|
|
if arguments.pp:
|
|
mdfile = tempfile.TemporaryFile(mode='w+')
|
|
MarkdownPP.MarkdownPP(
|
|
input=arguments.markdown_file, output=mdfile, modules=list(MarkdownPP.modules)
|
|
)
|
|
mdfile.seek(0)
|
|
else:
|
|
mdfile = arguments.markdown_file
|
|
if arguments.settings:
|
|
context = json.load(arguments.settings)
|
|
else:
|
|
context = None
|
|
context = novel_parser(mdfile, context = context)
|
|
doc.render(context)
|
|
arguments.output.close()
|
|
doc.save(arguments.output.name)
|
|
|
|
if __name__ == '__main__':
|
|
main() |