markion.py (2770B) - raw


      1 #!/usr/bin/env python3
      2 # Markion
      3 # Copyright (C) 2019-2020 Oscar Benedito <oscar@oscarbenedito.com>
      4 #
      5 # This program is free software: you can redistribute it and/or modify
      6 # it under the terms of the GNU Affero General Public License as
      7 # published by the Free Software Foundation, either version 3 of the
      8 # License, or (at your option) any later version.
      9 #
     10 # This program is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU Affero General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU Affero General Public License
     16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
     17 import os, sys, re, argparse
     18 __version__ = "1.0.0"
     19 parser = argparse.ArgumentParser(prog='Markion', description='Markion is a simple scripts that retrieves tangled code from Markdown.')
     20 parser.add_argument('file', metavar='file', type=str, nargs=1, help='Input file.')
     21 parser.add_argument('-d', '--output-directory', dest='out_dir', type=str, default=os.getcwd(), help='Change the output directory.')
     22 parser.add_argument('-D', '--auto-directory', dest='auto_dir', action='store_true', help='Auto detect output directory.')
     23 parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__)
     24 args = parser.parse_args()
     25 with open(args.file[0], 'r') as f:
     26     inp = f.read()
     27 r_block = '```[\w\-.]*\s+block\s+([\w.-]+).*?\n(.*?)\n```\s*?\n'
     28 r_file = '```[\w\-.]*\s+file\s+([\w.-]+).*?\n(.*?\n)```\s*?\n'
     29 blocks = re.findall(r_block, inp, flags = re.DOTALL)
     30 files = re.findall(r_file, inp, flags = re.DOTALL)
     31 r_include = re.compile('([ \t]*)\[\[\s*include\s+([\w\-.]+)\s*\]\]', flags = re.DOTALL)
     32 def resolve(content, blocks):
     33     it = r_include.finditer(content)
     34     for include in it:
     35         block_name = include[2]
     36         if blocks[block_name][0]:
     37             raise Exception('Circular dependency in block ' + block_name)
     38         blocks[block_name][0] = True
     39         s = resolve(blocks[block_name][1], blocks)
     40         blocks[block_name][0] = False
     41         blocks[block_name][1] = s
     42         s = include[1] + s.replace('\n', '\n' + include[1])
     43         content = r_include.sub(repr(s)[1:-1], content, count = 1)
     44     return content
     45 block_content = { b[0] : [False, b[1]] for b in blocks }
     46 file_content = dict()
     47 for f in files:
     48     if f[0] not in file_content:
     49         file_content[f[0]] = ''
     50     file_content[f[0]] += resolve(f[1], block_content)
     51 if args.auto_dir:
     52     args.out_dir = os.path.dirname(args.file[0])
     53 if not os.path.exists(args.out_dir):
     54     os.mkdirs(args.out_dir)
     55 for fn, fc in file_content.items():
     56     with open(os.path.join(args.out_dir, fn), 'w') as f:
     57         f.write(fc)