1
1
from cfbs .pretty import pretty_file
2
+ from cfbs .utils import user_error
3
+ import json
4
+ from shutil import which
2
5
import markdown_it
3
6
import os
4
7
import argparse
5
- import sys
8
+ import subprocess
6
9
7
10
8
- def extract_inline_code (file_path , languages ):
11
+ def extract_inline_code (path , languages ):
9
12
"""extract inline code, language and filters from markdown"""
10
13
11
- with open (file_path , "r" ) as f :
14
+ with open (path , "r" ) as f :
12
15
content = f .read ()
13
16
14
17
md = markdown_it .MarkdownIt ("commonmark" )
@@ -19,6 +22,9 @@ def extract_inline_code(file_path, languages):
19
22
if child .type != "fence" :
20
23
continue
21
24
25
+ if not child .info :
26
+ continue
27
+
22
28
info_string = child .info .split ()
23
29
language = info_string [0 ]
24
30
flags = info_string [1 :]
@@ -59,53 +65,99 @@ def get_markdown_files(start, languages):
59
65
return return_dict
60
66
61
67
62
- def extract (path , i , language , first_line , last_line ):
68
+ def extract (origin_path , snippet_path , _language , first_line , last_line ):
63
69
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 ()
66
73
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 ])
68
75
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 } '" )
71
80
72
81
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" )
75
111
76
112
77
113
def check_output ():
78
114
pass
79
115
80
116
81
- def replace ():
82
- pass
117
+ def replace (origin_path , snippet_path , _language , first_line , last_line ):
83
118
119
+ try :
120
+ with open (snippet_path , "r" ) as f :
121
+ pretty_content = f .read ()
84
122
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 " )
86
126
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
+ )
90
130
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
100
132
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 } '" )
103
141
104
- lines = origin_content .split ("\n " )
105
- lines [first_line + 1 : last_line - 1 ] = pretty_content .split ("\n " )
142
+ return offset
106
143
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" )
109
161
110
162
111
163
def parse_args ():
@@ -166,44 +218,72 @@ def parse_args():
166
218
args = parse_args ()
167
219
168
220
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" )
171
229
172
230
for language in args .languages :
173
231
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 ())} "
176
234
)
177
- sys .exit (- 1 )
178
235
179
236
parsed_markdowns = get_markdown_files (args .path , args .languages )
180
237
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 } "
183
251
184
252
if args .extract and "noextract" not in code_block ["flags" ]:
185
253
extract (
186
- path ,
187
- i + 1 ,
188
- supported_languages [ code_block [ " language" ]] ,
254
+ origin_path ,
255
+ snippet_path ,
256
+ language ,
189
257
code_block ["first_line" ],
190
258
code_block ["last_line" ],
191
259
)
192
260
193
261
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
+ )
195
269
196
270
if args .autoformat and "noautoformat" not in code_block ["flags" ]:
197
271
autoformat (
198
- path ,
199
- i + 1 ,
200
- supported_languages [ code_block [ " language" ]] ,
272
+ origin_path ,
273
+ snippet_path ,
274
+ language ,
201
275
code_block ["first_line" ],
202
276
code_block ["last_line" ],
203
277
)
204
278
205
- if args .replace and "noreplace" not in code_block ["flags" ]:
206
- replace ()
207
-
208
279
if args .output_check and "noexecute" not in code_block ["flags" ]:
209
280
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