Skip to content

Commit c5c0822

Browse files
authored
Merge pull request #3426 from victormlg/md-cf-promises
ENT-12737: Added check_syntax for cf3 in markdown parser
2 parents 2b582d5 + 82069c3 commit c5c0822

File tree

1 file changed

+129
-49
lines changed

1 file changed

+129
-49
lines changed

scripts/markdown-code-checker.py

Lines changed: 129 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
from cfbs.pretty import pretty_file
2+
from cfbs.utils import user_error
3+
import json
4+
from shutil import which
25
import markdown_it
36
import os
47
import argparse
5-
import sys
8+
import subprocess
69

710

8-
def extract_inline_code(file_path, languages):
11+
def extract_inline_code(path, languages):
912
"""extract inline code, language and filters from markdown"""
1013

11-
with open(file_path, "r") as f:
14+
with open(path, "r") as f:
1215
content = f.read()
1316

1417
md = markdown_it.MarkdownIt("commonmark")
@@ -19,6 +22,9 @@ def extract_inline_code(file_path, languages):
1922
if child.type != "fence":
2023
continue
2124

25+
if not child.info:
26+
continue
27+
2228
info_string = child.info.split()
2329
language = info_string[0]
2430
flags = info_string[1:]
@@ -59,53 +65,99 @@ def get_markdown_files(start, languages):
5965
return return_dict
6066

6167

62-
def extract(path, i, language, first_line, last_line):
68+
def extract(origin_path, snippet_path, _language, first_line, last_line):
6369

64-
with open(path, "r") as f:
65-
content = f.read()
70+
try:
71+
with open(origin_path, "r") as f:
72+
content = f.read()
6673

67-
code_snippet = "\n".join(content.split("\n")[first_line + 1 : last_line - 1])
74+
code_snippet = "\n".join(content.split("\n")[first_line + 1 : last_line - 1])
6875

69-
with open(f"{path}.snippet-{i}.{language}", "w") as f:
70-
f.write(code_snippet)
76+
with open(snippet_path, "w") as f:
77+
f.write(code_snippet)
78+
except IOError:
79+
user_error(f"Couldn't open '{origin_path}' or '{snippet_path}'")
7180

7281

73-
def check_syntax():
74-
pass
82+
def check_syntax(origin_path, snippet_path, language, first_line, _last_line):
83+
snippet_abs_path = os.path.abspath(snippet_path)
84+
85+
if not os.path.exists(snippet_path):
86+
user_error(
87+
f"Couldn't find the file '{snippet_path}'. Run --extract to extract the inline code."
88+
)
89+
90+
match language:
91+
case "cf":
92+
try:
93+
p = subprocess.run(
94+
["/var/cfengine/bin/cf-promises", snippet_abs_path],
95+
capture_output=True,
96+
text=True,
97+
)
98+
err = p.stderr
99+
100+
if err:
101+
err = err.replace(snippet_abs_path, f"{origin_path}:{first_line}")
102+
print(err)
103+
except OSError:
104+
user_error(f"'{snippet_abs_path}' doesn't exist")
105+
except ValueError:
106+
user_error("Invalid subprocess arguments")
107+
except subprocess.CalledProcessError:
108+
user_error(f"Couldn't run cf-promises on '{snippet_abs_path}'")
109+
except subprocess.TimeoutExpired:
110+
user_error("Timed out")
75111

76112

77113
def check_output():
78114
pass
79115

80116

81-
def replace():
82-
pass
117+
def replace(origin_path, snippet_path, _language, first_line, last_line):
83118

119+
try:
120+
with open(snippet_path, "r") as f:
121+
pretty_content = f.read()
84122

85-
def autoformat(path, i, language, first_line, last_line):
123+
with open(origin_path, "r") as f:
124+
origin_lines = f.read().split("\n")
125+
pretty_lines = pretty_content.split("\n")
86126

87-
match language:
88-
case "json":
89-
file_name = f"{path}.snippet-{i}.{language}"
127+
offset = len(pretty_lines) - len(
128+
origin_lines[first_line + 1 : last_line - 1]
129+
)
90130

91-
try:
92-
pretty_file(file_name)
93-
with open(file_name, "r") as f:
94-
pretty_content = f.read()
95-
except:
96-
print(
97-
f"[error] Couldn't find the file '{file_name}'. Run --extract to extract the inline code."
98-
)
99-
return
131+
origin_lines[first_line + 1 : last_line - 1] = pretty_lines
100132

101-
with open(path, "r") as f:
102-
origin_content = f.read()
133+
with open(origin_path, "w") as f:
134+
f.write("\n".join(origin_lines))
135+
except FileNotFoundError:
136+
user_error(
137+
f"Couldn't find the file '{snippet_path}'. Run --extract to extract the inline code."
138+
)
139+
except IOError:
140+
user_error(f"Couldn't open '{origin_path}' or '{snippet_path}'")
103141

104-
lines = origin_content.split("\n")
105-
lines[first_line + 1 : last_line - 1] = pretty_content.split("\n")
142+
return offset
106143

107-
with open(path, "w") as f:
108-
f.write("\n".join(lines))
144+
145+
def autoformat(_origin_path, snippet_path, language, _first_line, _last_line):
146+
147+
match language:
148+
case "json":
149+
try:
150+
pretty_file(snippet_path)
151+
except FileNotFoundError:
152+
user_error(
153+
f"Couldn't find the file '{snippet_path}'. Run --extract to extract the inline code."
154+
)
155+
except PermissionError:
156+
user_error(f"Not enough permissions to open '{snippet_path}'")
157+
except IOError:
158+
user_error(f"Couldn't open '{snippet_path}'")
159+
except json.decoder.JSONDecodeError:
160+
user_error(f"Invalid json")
109161

110162

111163
def parse_args():
@@ -166,44 +218,72 @@ def parse_args():
166218
args = parse_args()
167219

168220
if not os.path.exists(args.path):
169-
print("[error] This path doesn't exist")
170-
sys.exit(-1)
221+
user_error("This path doesn't exist")
222+
223+
if (
224+
args.syntax_check
225+
and "cf3" in args.languages
226+
and not which("/var/cfengine/bin/cf-promises")
227+
):
228+
user_error("cf-promises is not installed")
171229

172230
for language in args.languages:
173231
if language not in supported_languages:
174-
print(
175-
f"[error] Unsupported language '{language}'. The supported languages are: {", ".join(supported_languages.keys())}"
232+
user_error(
233+
f"Unsupported language '{language}'. The supported languages are: {", ".join(supported_languages.keys())}"
176234
)
177-
sys.exit(-1)
178235

179236
parsed_markdowns = get_markdown_files(args.path, args.languages)
180237

181-
for path in parsed_markdowns["files"].keys():
182-
for i, code_block in enumerate(parsed_markdowns["files"][path]["code-blocks"]):
238+
for origin_path in parsed_markdowns["files"].keys():
239+
offset = 0
240+
for i, code_block in enumerate(
241+
parsed_markdowns["files"][origin_path]["code-blocks"]
242+
):
243+
244+
# adjust line numbers after replace
245+
for cb in parsed_markdowns["files"][origin_path]["code-blocks"][i:]:
246+
cb["first_line"] += offset
247+
cb["last_line"] += offset
248+
249+
language = supported_languages[code_block["language"]]
250+
snippet_path = f"{origin_path}.snippet-{i+1}.{language}"
183251

184252
if args.extract and "noextract" not in code_block["flags"]:
185253
extract(
186-
path,
187-
i + 1,
188-
supported_languages[code_block["language"]],
254+
origin_path,
255+
snippet_path,
256+
language,
189257
code_block["first_line"],
190258
code_block["last_line"],
191259
)
192260

193261
if args.syntax_check and "novalidate" not in code_block["flags"]:
194-
check_syntax()
262+
check_syntax(
263+
origin_path,
264+
snippet_path,
265+
language,
266+
code_block["first_line"],
267+
code_block["last_line"],
268+
)
195269

196270
if args.autoformat and "noautoformat" not in code_block["flags"]:
197271
autoformat(
198-
path,
199-
i + 1,
200-
supported_languages[code_block["language"]],
272+
origin_path,
273+
snippet_path,
274+
language,
201275
code_block["first_line"],
202276
code_block["last_line"],
203277
)
204278

205-
if args.replace and "noreplace" not in code_block["flags"]:
206-
replace()
207-
208279
if args.output_check and "noexecute" not in code_block["flags"]:
209280
check_output()
281+
282+
if args.replace and "noreplace" not in code_block["flags"]:
283+
offset = replace(
284+
origin_path,
285+
snippet_path,
286+
language,
287+
code_block["first_line"],
288+
code_block["last_line"],
289+
)

0 commit comments

Comments
 (0)