#!/usr/bin/env python3 from pathlib import Path import os import re import itertools import argparse class Line(object): """ Base class for a document line. """ def __init__(self, content): self.content = content def __str__(self): return self.content class CommentLine(Line): """ A line that was a comment in the source document. It will be outputed as normal text. """ pass class CodeLine(Line): """ A line that was source code in the source document. It will be outputed as quoted code. """ pass class Block(object): """ Base class for a block of lines. This class represents normal, unmodified text. """ def __init__(self): self.lines = list() def add_line(self, line): """ Add another line to the block. `line` is supposed to be a `Line` object and has to be of the same type as the other lines in this block. """ self.lines.append(line) def __str__(self): return "\n".join(map(str, self.lines)) class CodeBlock(Block): """ A block of code lines. It has additional features to export the lines as quoted code. """ def __init__(self, language): self.language = language self.lines = list() def __str__(self): return "\n```{}\n".format(self.language) + "\n".join(map(str, self.lines)) + "\n```\n" class File(object): def __init__(self, path, language=None): self.path = path # Determine the language of the source. if language is None: language = re.match(r".([^\n]*)", path.suffix).group(1) # Read the raw lines fromt the input file. with path.open("r") as input: raw_lines = input.readlines() # Retrieve an iterator over all line object. lines = make_lines(raw_lines, language) # Retrieve an iterator over all blocks. self.blocks = list(lines_to_blocks(lines, language)) def __str__(self): return "### {}\n\n".format(str(self.path)) + "\n".join(map(str, self.blocks)) class Chapter(object): def __init__(self, intro_path, paths): self.intro = "".join(open(intro_path, "r").readlines()) self.files = list() for path in paths: self.files.append(File(Path(path))) def __str__(self): return self.intro + "\n" + "\n".join(map(str, self.files)) def make_lines(raw_lines, language): """ Iterate through the raw lines and yield either a `CommentLine` or a `CodeLine`. """ # Depending on the language, other comment indicators are used. if language == "rs": comment_indicator_re = re.compile(r"\s*//\s*([^\n]*)") else: comment_indicator_re = re.compile(r"\s*#\s*([^\n]*)") # A RE to clean a code line of the new-line character and to remove empty code lines. clean_line_re = re.compile(r"([^\n]*)") for line in raw_lines: is_comment = comment_indicator_re.match(line) if is_comment: yield CommentLine(is_comment.group(1)) else: cleaned_line = clean_line_re.match(line) if cleaned_line is not None: yield CodeLine(cleaned_line.group(1)) def lines_to_blocks(lines, language): """ Iterate through the lines and group them in blocks. """ last_block = None for line in lines: if last_block is None: new_block = True elif type(last_block.lines[-1]) != type(line): yield last_block new_block = True else: new_block = False if new_block: if type(line) == CodeLine: last_block = CodeBlock(language) else: last_block = Block() last_block.add_line(line) yield last_block def make(): try: os.mkdir("chapter") except FileExistsError: pass amp = Chapter("introductions/amp.md", [ "amp/eg-amp-rs.lv2/manifest.ttl", "amp/eg-amp-rs.lv2/amp.ttl", "amp/Cargo.toml", "amp/src/lib.rs", ]) open("chapter/amp.md", "w").write(str(amp)) midigate = Chapter("introductions/midigate.md", [ "midigate/eg-midigate-rs.lv2/manifest.ttl", "midigate/eg-midigate-rs.lv2/midigate.ttl", "midigate/Cargo.toml", "midigate/src/lib.rs" ]) open("chapter/midigate.md", "w").write(str(midigate)) fifths = Chapter("introductions/fifths.md", [ "fifths/eg-fifths-rs.lv2/manifest.ttl", "fifths/eg-fifths-rs.lv2/fifths.ttl", "fifths/Cargo.toml", "fifths/src/lib.rs" ]) open("chapter/fifths.md", "w").write(str(fifths)) metro = Chapter("introductions/metro.md", [ "metro/eg-metro-rs.lv2/manifest.ttl", "metro/eg-metro-rs.lv2/metro.ttl", "metro/Cargo.toml", "metro/src/pipes.rs", "metro/src/lib.rs" ]) open("chapter/metro.md", "w").write(str(metro))