Compare commits

...

3 Commits

Author SHA1 Message Date
Dmytro Yeroshkin 314341ff9e Added setup.py 2022-01-11 17:01:48 +01:00
Dmytro Yeroshkin bdb2d54159 Initial Commit 2022-01-11 17:01:20 +01:00
Dmytro Yeroshkin c7bb77db7d Updated Licence and Readme 2022-01-11 17:00:53 +01:00
6 changed files with 202 additions and 1 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) <year> <owner>. All rights reserved.
Copyright (c) 2022 Dmytro Yeroshkin. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View File

@ -1,2 +1,25 @@
# novel-compiler
This is a simple python script that compiles a novel written in markdown into a Microsoft Word `.docx` file.
Dependencies: `docxtpl`, `markdown`, `MarkdownPP`, `novel_stats`. The first three can be installed using `pip`, and the last one is available at [https://projects.torsion.org/witten/novel-stats]().
Formatting:
```md
# PROJECT TITLE
### AUTHOR NAME
## CHAPTER HEADING
```
Currently the only formatting supported is italics and bold (`* and **`).
A sample format is included in the distribution.
## Supported arguments
* `-o [output.docx]` or `--output [output.docx]` - the file to which the formatted novel is to be written
* `-t [template.docx]` or `--template [template.docx]` - provide a template file
* `-s [settings.json]` or `--settings [settings.json]` - provide a file with information to be filled into the template
* `-pp` - use a preprocessor to enable multi-file novels
* `[markdown_file]` - name of the novel source file

View File

@ -0,0 +1,146 @@
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 = '<w:r><w:t xml:space="preserve">'
post = '</w:t></w:r>'
it_pre = '<w:r><w:rPr><w:i/></w:rPr><w:t xml:space="preserve">'
bf_pre = '<w:r><w:rPr><w:b/></w:rPr><w:t xml:space="preserve">'
bfit_pre = '<w:r><w:rPr><w:b/><w:i/></w:rPr><w:t xml:space="preserve">'
# Tag replacement
html = markdown.markdown(md_paragraph)
html = html.replace('<p>', pre)
html = html.replace('</p>', post)
html = html.replace('<strong><em>', post+bfit_pre)
html = html.replace('</strong></em>', post+pre)
html = html.replace('<em>', post+it_pre)
html = html.replace('</em>', post+pre)
html = html.replace('<strong>', post+bf_pre)
html = html.replace('</strong>', 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()

View File

@ -0,0 +1,6 @@
{
"author_address": "Address",
"author_email": "Email",
"author_phone": "Phone number",
"author_website": "Website"
}

Binary file not shown.

26
setup.py Normal file
View File

@ -0,0 +1,26 @@
from setuptools import find_packages, setup
VERSION = "0.1.0"
setup(
name="novel-compiler",
version=VERSION,
description="Convert a markdown novel to a docx file in a standard style for sharing or submission.",
author="Dmytro Yeroshkin",
author_email="dmytro.yeroshkin@gmail.com",
url="https://projects.torsion.org/",
classifiers=[
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Other Audience",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Topic :: Office/Business",
],
packages=find_packages(),
entry_points={"console_scripts": ["novel-compiler = novel_compiler.novel_compiler:main",]},
install_requires=('docxtpl','markdown','novel_stats','MarkdownPP'),
include_package_data=True,
python_requires='>3.7.0',
)