11from cfbs .pretty import pretty_file
2+ from cfbs .utils import user_error
3+ import json
4+ from shutil import which
25import markdown_it
36import os
47import 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
77113def 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
111163def 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