Path: blob/main/bin/check-broken-boxes.py
1677 views
#!/usr/bin/env python1import copy2import re3import sys4import logging56# read in a tutorial, and check the structure of it.7file = sys.argv[1]8if len(sys.argv) > 2 and sys.argv[2] == '--debug':9logging.basicConfig(level=logging.DEBUG)10else:11logging.basicConfig(level=logging.ERROR)1213tuto = open(file, 'r')1415boxes = r'^([\s>]*>[\s>]*)'16box_open = r'<([a-z-]*)-title>(.*)<\/[a-z-]*-title>'17box_close = r'{:\s*(\.[^}]*)\s*}'18whitespace = r'^(\s*)'19pre = r'^```'20in_pre = False2122tag_stack = []23prev_depth = 0242526def stripN(line, count):27c = copy.copy(line)28for i in range(count):29c = c.lstrip()30c = c.lstrip('>')31c = c.lstrip()32return c3334def skippable_tag(tags, check):35for tag in tags:36if tag in check:37return True38return False3940BASE_SKIPPABLE_TAGS = ['hidden', 'quote', 'spoken', 'code-2col']41BASE_EXPECTED_TAGS = [42'hands_on',43'tip',44'details'45]4647for line, text in enumerate(tuto.read().split('\n')):4849m = re.match(boxes, text)50if m:51depth = m.group(1).count('>')52else:53depth = 054logging.debug(f'A|{"[pre]" if in_pre else ""} depth={depth} line={line} {text:120s} tss={[x["tag"] for x in tag_stack]}')5556mw = re.match(whitespace, text).group(1)5758# Handling pre toggling59unprefixed = stripN(text, depth)60pre_toggle = re.match(pre, unprefixed)6162if not in_pre and depth > prev_depth:63unprefixed = stripN(text, depth)64m1 = re.match(box_open, unprefixed)65if m1:66tag = m1.group(1).replace('-', '_')67logging.debug(f'B|{"[pre]" if in_pre else ""} line={line} {mw}{" " * (depth - 1)}<{tag}>')6869tag_stack.append({70'tag': tag,71'line': line,72'text': text,73'mw': mw,74})75else:76logging.debug(f'C|{"[pre]" if in_pre else ""} LINE={line} {mw}{" " * (depth - 1)}<NONE>')77tag_stack.append({78'tag': None,79'line': line,80'text': text,81'mw': mw,82})83logging.debug(f"D|{'[pre]' if in_pre else ''} {line} Potential Quote/Hidden/Spoken")84# error?8586elif not in_pre and depth < prev_depth:87unprefixed = stripN(text, depth)88m1 = re.match(box_close, unprefixed.strip())89logging.debug(f'E|{"[pre]" if in_pre else ""} prev={prev_depth} -> curr={depth} line={line} m1={m1} ({box_close} =~ {unprefixed}) ts={len(tag_stack)} tss={[x["tag"] for x in tag_stack]}')90if m1 is None:91message = f"Potential broken box. A {tag_stack[-1]['tag']} was opened on {tag_stack[-1]['line']}, but not closed on line {line}"92print(f"{file}:{line}: {message}")93logging.warning(f"{file}:{line}: {message}")94logging.debug(f'F|{"[pre]" if in_pre else ""} NONE {line} |{text}|')95logging.debug(tag_stack[-1])96logging.debug(f"{mw}{' ' * (depth)}</NONE>")97tag_stack.pop()98else:99if any([skippable_tag(BASE_SKIPPABLE_TAGS, t) for t in m1.groups()]):100logging.debug(f"G|{'[pre]' if in_pre else ''} {mw}{' ' * (depth)}</NONE-skip tag={m1.groups()[0]}>")101logging.debug(f'H|NONE {line} |{text}|')102if ('code-2col' in m1.groups()[0] or 'hidden' in m1.groups()[0]) and (len(tag_stack) == 0 or tag_stack[-1]['tag'] is not None):103pass104# This is a special case.105# Here we don't have tag_stack[-1]['tag'] is None106# Because there wasn't even a blank header line before the 2col started.107else:108tag_stack.pop()109else:110closing_tags = m1.groups(1)[0].replace('-', '_').lstrip('.').split('.')111closing_tag = closing_tags[0].strip()112logging.debug(tag_stack[-1])113logging.debug(f"I|{mw}{' ' * (depth)}</{closing_tag}>")114115if len(tag_stack) == 0:116message = f"Potential broken was closed with {closing_tag} on line {line}"117print(f"{file}:{line}: {message}")118logging.warning(f"{file}:{line}: {message}")119if tag_stack[-1]['tag'] == closing_tag:120p = tag_stack.pop()121else:122logging.debug(f'J|{"[pre]" if in_pre else ""} prev={prev_depth} -> curr={depth} line={line} m={m1} c={closing_tags}')123if not (tag_stack[-1]['tag'] is None and closing_tag not in BASE_EXPECTED_TAGS):124message = f"A {tag_stack[-1]['tag']} was opened, but closed with {closing_tag} on line {line}"125print(f"{file}:{line}: {message}")126logging.warning(f"{file}:{line}: {message}")127else:128p = tag_stack.pop()129else:130pass131#unprefixed = stripN(text, depth)132#pre_toggle = re.match(pre, unprefixed)133#if pre_toggle:134# in_pre = not in_pre135# logging.debug(f'{"[pre]" if in_pre else ""} line={line} PRE {"OPEN" if in_pre else "CLOSE"} {text}')136if pre_toggle:137in_pre = not in_pre138139140prev_depth = depth141142143