This is the documentation, and source code, for md-tangle
.
md-tangle
is supported by both Python 2 and Python 3.
Since some still swear to Python 2, I had to do some adaptations.
I would prefer to use:
Typings
(introduced in Python 3.5)os.makedirs(dir, exist_ok=True)
instead ofPath(dir).mkdir(exist_ok=True)
f"{filename:50} {lines} lines"
instead of"{0: <50} {1} lines".format(filename, lines)
If installed with Python 2, pathlib2
is a requirement. Since it's not part of the
requirements for the Python 3 package, it's not listed in the requirements.
__title__ = 'md-tangle'
__version__ = '1.3.1'
__author__ = 'Joakim Myrvoll Johansen'
__author_email__ = 'joakimmyrvoll@gmail.com'
__license__ = 'MIT'
from . import main
main.main()
Imports
import argparse
import sys
import md_tangle
from md_tangle.save import save_to_file
from md_tangle.tangle import map_md_to_code_blocks
Setup for all arguments
def __get_args():
parser = argparse.ArgumentParser(description="Tangle code blocks from Markdown file.")
parser.add_argument("filename", type=str, help="path to Markdown file", nargs='?')
parser.add_argument("--version", action='store_true', help="print version")
parser.add_argument("-v", "--verbose", action='store_true', help="show output")
parser.add_argument("-f", "--force", action='store_true', help="force overwrite of files")
parser.add_argument("-d", "--destination", type=str, help="overwrite output destination")
parser.add_argument("-s", "--separator", type=str, help="separator for tangle destinations (default=',')", default=",")
return parser.parse_args()
def main():
"""Main program entry point"""
args = __get_args()
if args.version:
print(md_tangle.__version__)
sys.exit(0)
if args.filename is None:
sys.stderr.write("The 'filename' argument is required.\n")
sys.exit(1)
blocks = map_md_to_code_blocks(args.filename, args.separator)
save_to_file(blocks, args.verbose, args.force, args.destination)
if __name__ == '__main__':
main()
Imports
import re
from io import open
These are the different Regex's for finding code blocks, and the tangle keyword.
TANGLE_CMD = "tangle:"
TANGLE_REGEX = "tangle:+([^\s]+)"
BLOCK_REGEX = "~{4}|`{3}"
BLOCK_REGEX_START = "^(~{4}|`{3})"
This function check if the line starts with one of the code block separators, and
it checks that it is only one on that line. So this
is not read as a code block.
def __contains_code_block_separators(line):
line = line.lstrip(' ')
tangle = re.search(BLOCK_REGEX_START, line)
starts_with_separator = tangle is not None
tangle = re.findall(BLOCK_REGEX, line)
only_one_separator = len(tangle) == 1
return starts_with_separator and only_one_separator
If the line includes one code block separator, this function will try to extract the tangle keyword.
def __get_save_locations(line, separator):
tangle = re.search(TANGLE_REGEX, line)
if tangle is None:
return None
match = tangle.group(0)
locations = match.replace(TANGLE_CMD, '')
return locations.split(separator)
These functions simply add the lines in the code blocks to it's destinations. The format on this data model is:
code_blocks = {
"path/filename": "text from code block",
"path/filename2": "text from code block",
}
implementation
def __add_to_code_blocks(code_blocks, locations, line):
for location in locations:
code_blocks[location] = code_blocks.get(location, "") + line
def map_md_to_code_blocks(filename, separator):
md_file = open(filename, "r", encoding="utf8")
lines = md_file.readlines()
locations = None
code_blocks = {}
for line in lines:
if __contains_code_block_separators(line):
locations = __get_save_locations(line, separator)
elif locations is not None:
__add_to_code_blocks(code_blocks, locations, line)
md_file.close()
return code_blocks
Imports
os.makedirs
does not support creating paths if they already exists in Python 2. So we need use Path
from
pathlib
/pathlib2
(backport for Python 2).
import os
from io import open
try:
get_input = raw_input # fix for Python 2
except NameError:
get_input = input
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path # Python 2 backport
Creates directory if not existing
def __create_dir(path):
dir_name = os.path.dirname(path)
if dir_name is not "":
Path(dir_name).mkdir(exist_ok=True)
This function writes the code blocks to it's destinations.
def save_to_file(code_blocks, verbose=False, force=False, output_dest=None):
for path, value in code_blocks.items():
path = os.path.expanduser(path)
if output_dest is not None:
path = output_dest + "/" + os.path.basename(path)
__create_dir(path)
if os.path.isfile(path) and not force:
overwrite = get_input("'{0}' already exists. Overwrite? (Y/n) ".format(path))
if overwrite != "" and overwrite.lower() != "y":
continue
with open(path, "w", encoding="utf8") as f:
f.write(value)
f.close()
if verbose:
print("{0: <50} {1} lines".format(path, len(value.splitlines())))