Markdown Converter
Sometimes I write books (or try too, at least). I write in a single markdown file because I find this works for me.
This does, however, pose a problem. You’ve probably notied this website is a
mdbook instance that I’ve used as a blog. Needless to say, I think mdbook
is really cool.
Unfortunately, mdbook isn’t great for single markdown files – by which I
mean it doesn’t work at all. I had a quick look on the web, but couldn’t find
anything that would convert for me quick and easily. So I wrote my own! And in
the spirit of free software, it is below. Whilst I’d prefer to use PyQt, I
have aproject going on at my real work that needs tkinter so I used this as a
little practice.
Any problems? Let me know at kay at kayjoseph dot xyz.
#!/usr/bin/env python
'''
Markdown to mdbook converter
Kay Saint Joseph
This program is licensed under the GNU General Public License v3
'''
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
import sys
import os
## convenience function
def read_file(fname) -> list:
with open(fname, 'r') as f:
return f.readlines()
## removes comments from the readlines book. Comments are HTML "<!-- ... -->"
def remove_comments(f_lines) -> list:
comment = False
ret = []
for line in f_lines:
if "<!--" in line:
comment = True
if not comment:
ret.append(line)
if "-->" in line:
comment = False
return ret
# take lines of book. return list of mdbook style SUMMARY.md
def create_summary(lines: list) -> list:
summary = []
titles = filter(lambda x: x[0]=='#', lines)
for title in titles:
section = len(list(filter(lambda x: x=='#', title)))
name = title.lstrip(' #').rstrip()
md = get_chapter_md(title)
if section == 1:
summary.append("# {}".format(name))
else:
summary.append(" - [{}]({})".format(name, md))
summary = list(map(lambda x: x+'\n', summary))
return summary
## takes every line in book and returns dict {chapter name: [line 1, line 2]}
def create_chapters(lines: list) -> dict:
ret_dict = {}
chapter_title = ""
chapter_text = []
for line in lines:
if line[0] == '#':
if chapter_title != "":
ret_dict[chapter_title] = chapter_text
chapter_title = line
chapter_text = []
else:
chapter_text.append(line)
else:
ret_dict[chapter_title] = chapter_text
return ret_dict
## debug func
def printdict(d):
for (k,v) in d.items():
print(k+": "+str(v[0:3]))
## Take a chapter name such as "The Bog" and turn it into a md file name like
## "thebog.md"
def get_chapter_md(s: str) -> str:
s = s.lstrip(' #').lower().replace(' ', '_')
s = ''.join(filter(lambda x: x.isalpha() or x==' ', s))
s = s + ".md"
return s
## Add a front page for the mdbook
def write_preamble(s: str):
with open("src/preamble.md", "w") as f:
f.writelines(s)
## where the actual work is done
def convert_to_mdbook(book_name: str, author: str, main_md="./main.md", ):
book = read_file(main_md)
book = remove_comments(book)
summary = create_summary(book)
book = create_chapters(book)
preamble = ['# ' + book_name + "\n\n", author + "\n"]
write_preamble(preamble)
with open("src/SUMMARY.md", "w") as f:
f.write("[{}](preamble.md)\n".format(book_name))
f.writelines(summary)
for (k,v) in book.items():
fname = "src/" + get_chapter_md(k)
with open(fname, "w") as f:
head = "<b>" + k.lstrip(' #') + "</b>\n\n"
f.write(head)
f.writelines(v)
## GUI
class App(Frame):
def __init__(self, master):
super().__init__(master)
self.pack()
ttk.Label(text="MD Booker").pack(pady=10)
self.t_cont = StringVar()
self.a_cont = StringVar()
self.main_md_file = StringVar()
ttk.Label(text="Title").pack(pady=10)
self.title = Entry(textvariable=self.t_cont).pack(padx=10)
ttk.Label(text="Author").pack()
self.author = Entry(textvariable=self.a_cont).pack(padx=10)
self.file_label = ttk.Label().pack(side="right")
self.md_button = ttk.Button(
text="Pick Markdown File", command=self.md_filepick
).pack(pady=10)
ttk.Button(
text="Run",
command=self.process
).pack(side="left", pady=10)
ttk.Button(text="Quit", command=kill_fail).pack(side="right", pady=10)
def get_title(self):
return self.t_cont.get()
def get_author(self):
return self.a_cont.get()
## take the GUI entered info and run the converter itself
def process(self):
t = self.get_title()
a = self.get_author()
f = self.main_md_file.get()
if t != "" and a != "" and f != "":
# print("Title: " + t)
# print("Author: " + a)
# print("Main Markdown File: " + f)
convert_to_mdbook(t, a, main_md=f)
else:
raise ValueError("Boxes Empty")
ttk.messagebox.showinfo("Error", "Boxes Empty")
sys.exit(0)
## pick the main book markdown file to convert
def md_filepick(self):
i_dir = os.getcwd()
filename = filedialog.askopenfilename(
initialdir = i_dir,
title = "Pick File"
)
self.main_md_file.set(filename)
self.md_button.config(text=filename)
return filename
## Option setter from entered args
class Opt():
def __init__(self, args):
self.is_gui = False
if "-h" in args:
raise UsageError
elif "-g" in args:
self.is_gui = True
else:
if len(args) != 7:
raise SyntaxError("Error: too many arguments")
for i in range(1, len(args)-1):
if args[i] == "-a":
self.author = args[i+1]
elif args[i] == "-t":
self.title = args[i+1]
elif args[i] == "-f":
self.md_file = args[i+1]
else:
continue
# print("Title: " + self.title)
# print("Author: " + self.author)
# print("Markdown file: " + self.md_file)
class UsageError(Exception):
pass
def kill_fail():
sys.exit(1)
def usage():
print(
'''\
MD Converter: a utility for turning a single markdown book into an mdbook
compliant setup.
Usage: mdconverter -g
mdconverter -a <author> -t <title> -f <single_markdown_file>
mdconverter -h
'''
)
kill_fail()
def main(args):
try:
opt = Opt(args)
except SyntaxError as e:
print(e)
usage()
except UsageError:
usage()
if opt.is_gui:
root = Tk()
gui = App(root)
gui.master.title("MD Book Converter")
gui.mainloop()
else:
try:
convert_to_mdbook(opt.title, opt.author, main_md=opt.md_file)
except:
usage()
if __name__ == "__main__":
main(sys.argv)