Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
galaxyproject
GitHub Repository: galaxyproject/training-material
Path: blob/main/bin/check-broken-boxes.py
1677 views
1
#!/usr/bin/env python
2
import copy
3
import re
4
import sys
5
import logging
6
7
# read in a tutorial, and check the structure of it.
8
file = sys.argv[1]
9
if len(sys.argv) > 2 and sys.argv[2] == '--debug':
10
logging.basicConfig(level=logging.DEBUG)
11
else:
12
logging.basicConfig(level=logging.ERROR)
13
14
tuto = open(file, 'r')
15
16
boxes = r'^([\s>]*>[\s>]*)'
17
box_open = r'<([a-z-]*)-title>(.*)<\/[a-z-]*-title>'
18
box_close = r'{:\s*(\.[^}]*)\s*}'
19
whitespace = r'^(\s*)'
20
pre = r'^```'
21
in_pre = False
22
23
tag_stack = []
24
prev_depth = 0
25
26
27
def stripN(line, count):
28
c = copy.copy(line)
29
for i in range(count):
30
c = c.lstrip()
31
c = c.lstrip('>')
32
c = c.lstrip()
33
return c
34
35
def skippable_tag(tags, check):
36
for tag in tags:
37
if tag in check:
38
return True
39
return False
40
41
BASE_SKIPPABLE_TAGS = ['hidden', 'quote', 'spoken', 'code-2col']
42
BASE_EXPECTED_TAGS = [
43
'hands_on',
44
'tip',
45
'details'
46
]
47
48
for line, text in enumerate(tuto.read().split('\n')):
49
50
m = re.match(boxes, text)
51
if m:
52
depth = m.group(1).count('>')
53
else:
54
depth = 0
55
logging.debug(f'A|{"[pre]" if in_pre else ""} depth={depth} line={line} {text:120s} tss={[x["tag"] for x in tag_stack]}')
56
57
mw = re.match(whitespace, text).group(1)
58
59
# Handling pre toggling
60
unprefixed = stripN(text, depth)
61
pre_toggle = re.match(pre, unprefixed)
62
63
if not in_pre and depth > prev_depth:
64
unprefixed = stripN(text, depth)
65
m1 = re.match(box_open, unprefixed)
66
if m1:
67
tag = m1.group(1).replace('-', '_')
68
logging.debug(f'B|{"[pre]" if in_pre else ""} line={line} {mw}{" " * (depth - 1)}<{tag}>')
69
70
tag_stack.append({
71
'tag': tag,
72
'line': line,
73
'text': text,
74
'mw': mw,
75
})
76
else:
77
logging.debug(f'C|{"[pre]" if in_pre else ""} LINE={line} {mw}{" " * (depth - 1)}<NONE>')
78
tag_stack.append({
79
'tag': None,
80
'line': line,
81
'text': text,
82
'mw': mw,
83
})
84
logging.debug(f"D|{'[pre]' if in_pre else ''} {line} Potential Quote/Hidden/Spoken")
85
# error?
86
87
elif not in_pre and depth < prev_depth:
88
unprefixed = stripN(text, depth)
89
m1 = re.match(box_close, unprefixed.strip())
90
logging.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]}')
91
if m1 is None:
92
message = f"Potential broken box. A {tag_stack[-1]['tag']} was opened on {tag_stack[-1]['line']}, but not closed on line {line}"
93
print(f"{file}:{line}: {message}")
94
logging.warning(f"{file}:{line}: {message}")
95
logging.debug(f'F|{"[pre]" if in_pre else ""} NONE {line} |{text}|')
96
logging.debug(tag_stack[-1])
97
logging.debug(f"{mw}{' ' * (depth)}</NONE>")
98
tag_stack.pop()
99
else:
100
if any([skippable_tag(BASE_SKIPPABLE_TAGS, t) for t in m1.groups()]):
101
logging.debug(f"G|{'[pre]' if in_pre else ''} {mw}{' ' * (depth)}</NONE-skip tag={m1.groups()[0]}>")
102
logging.debug(f'H|NONE {line} |{text}|')
103
if ('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):
104
pass
105
# This is a special case.
106
# Here we don't have tag_stack[-1]['tag'] is None
107
# Because there wasn't even a blank header line before the 2col started.
108
else:
109
tag_stack.pop()
110
else:
111
closing_tags = m1.groups(1)[0].replace('-', '_').lstrip('.').split('.')
112
closing_tag = closing_tags[0].strip()
113
logging.debug(tag_stack[-1])
114
logging.debug(f"I|{mw}{' ' * (depth)}</{closing_tag}>")
115
116
if len(tag_stack) == 0:
117
message = f"Potential broken was closed with {closing_tag} on line {line}"
118
print(f"{file}:{line}: {message}")
119
logging.warning(f"{file}:{line}: {message}")
120
if tag_stack[-1]['tag'] == closing_tag:
121
p = tag_stack.pop()
122
else:
123
logging.debug(f'J|{"[pre]" if in_pre else ""} prev={prev_depth} -> curr={depth} line={line} m={m1} c={closing_tags}')
124
if not (tag_stack[-1]['tag'] is None and closing_tag not in BASE_EXPECTED_TAGS):
125
message = f"A {tag_stack[-1]['tag']} was opened, but closed with {closing_tag} on line {line}"
126
print(f"{file}:{line}: {message}")
127
logging.warning(f"{file}:{line}: {message}")
128
else:
129
p = tag_stack.pop()
130
else:
131
pass
132
#unprefixed = stripN(text, depth)
133
#pre_toggle = re.match(pre, unprefixed)
134
#if pre_toggle:
135
# in_pre = not in_pre
136
# logging.debug(f'{"[pre]" if in_pre else ""} line={line} PRE {"OPEN" if in_pre else "CLOSE"} {text}')
137
if pre_toggle:
138
in_pre = not in_pre
139
140
141
prev_depth = depth
142
143