import argparse from docxtpl import DocxTemplate, RichText import markdown from novel_stats.novel_stats import count_words import tempfile import MarkdownPP import json 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 = [] def md_re_parser(md_paragraph): # Correct xml tags pre = '' post = '' it_pre = '' bf_pre = '' bfit_pre = '' # Tag replacement html = markdown.markdown(md_paragraph) html = html.replace('

', pre) html = html.replace('

', post) html = html.replace('', post+bfit_pre) html = html.replace('', post+pre) html = html.replace('', post+it_pre) html = html.replace('', post+pre) html = html.replace('', post+bf_pre) html = html.replace('', post+pre) # xml cleanup while pre+post in html: html = html.replace(pre+post,'') # convert to a rich text paragraph par = RichText() par.xml = html if len(html) == 0: print(md_paragraph) 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'} 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['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() arguments.template.close() doc = DocxTemplate(arguments.template.name) 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()