55# The MIT License (MIT)
66#
77# Copyright (c) 2020 Damien P. George
8- # Copyright (c) 2020 Jim Mussared
8+ # Copyright (c) 2023 Jim Mussared
99#
1010# Permission is hereby granted, free of charge, to any person obtaining a copy
1111# of this software and associated documentation files (the "Software"), to deal
2525# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2626# THE SOFTWARE.
2727
28+ # This is based on tools/codeformat.py from the main micropython/micropython
29+ # repository but without support for .c/.h files.
30+
2831import argparse
2932import glob
3033import itertools
3437
3538# Relative to top-level repo dir.
3639PATHS = [
37- # C
38- "**/*.[ch]" ,
39- # Python
4040 "**/*.py" ,
4141]
4242
4545# Path to repo top-level dir.
4646TOP = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
4747
48- UNCRUSTIFY_CFG = os .path .join (TOP , "tools/uncrustify.cfg" )
49-
50- C_EXTS = (
51- ".c" ,
52- ".h" ,
53- )
5448PY_EXTS = (".py" ,)
5549
5650
57- MAIN_BRANCH = "master"
58- BASE_BRANCH = os .environ .get ("GITHUB_BASE_REF" , MAIN_BRANCH )
59-
60-
6151def list_files (paths , exclusions = None , prefix = "" ):
6252 files = set ()
6353 for pattern in paths :
@@ -67,128 +57,33 @@ def list_files(paths, exclusions=None, prefix=""):
6757 return sorted (files )
6858
6959
70- def fixup_c (filename ):
71- # Read file.
72- with open (filename ) as f :
73- lines = f .readlines ()
74-
75- # Write out file with fixups.
76- with open (filename , "w" , newline = "" ) as f :
77- dedent_stack = []
78- while lines :
79- # Get next line.
80- l = lines .pop (0 )
81-
82- # Dedent #'s to match indent of following line (not previous line).
83- m = re .match (r"( +)#(if |ifdef |ifndef |elif |else|endif)" , l )
84- if m :
85- indent = len (m .group (1 ))
86- directive = m .group (2 )
87- if directive in ("if " , "ifdef " , "ifndef " ):
88- l_next = lines [0 ]
89- indent_next = len (re .match (r"( *)" , l_next ).group (1 ))
90- if indent - 4 == indent_next and re .match (r" +(} else |case )" , l_next ):
91- # This #-line (and all associated ones) needs dedenting by 4 spaces.
92- l = l [4 :]
93- dedent_stack .append (indent - 4 )
94- else :
95- # This #-line does not need dedenting.
96- dedent_stack .append (- 1 )
97- else :
98- if dedent_stack [- 1 ] >= 0 :
99- # This associated #-line needs dedenting to match the #if.
100- indent_diff = indent - dedent_stack [- 1 ]
101- assert indent_diff >= 0
102- l = l [indent_diff :]
103- if directive == "endif" :
104- dedent_stack .pop ()
105-
106- # Write out line.
107- f .write (l )
108-
109- assert not dedent_stack , filename
110-
111-
112- def query_git_files (verbose ):
113- def cmd_result_set (cmd ):
114- ret = subprocess .run (cmd , capture_output = True ).stdout .strip ().decode ()
115- if not ret :
116- return set ()
117- return {f .strip () for f in ret .split ("\n " )}
118-
119- def rel_paths (files , root ):
120- return {os .path .relpath (os .path .join (root , f .strip ()), "." ) for f in files }
121-
122- try :
123- ret = set ()
124-
125- # get path to root of repository
126- root_dir = (
127- subprocess .run (["git" , "rev-parse" , "--show-toplevel" ], capture_output = True )
128- .stdout .strip ()
129- .decode ()
130- )
131-
132- # Check locally modified files
133- status = cmd_result_set (["git" , "status" , "--porcelain" ])
134- dirty_files = rel_paths ({line .split (" " , 1 )[- 1 ] for line in status }, root_dir )
135- ret |= dirty_files
136-
137- # Current commit and branch
138- current_commit = (
139- subprocess .run (["git" , "rev-parse" , "HEAD" ], capture_output = True )
140- .stdout .strip ()
141- .decode ()
142- )
143- current_branches = cmd_result_set (["git" , "branch" , "--contains" , current_commit ])
144- if MAIN_BRANCH in current_branches :
145- if ret :
146- if verbose :
147- print ("Local changes detected, only scanning them." )
148- return ret
149-
150- # We're on clean master, run on entire repo
151- if verbose :
152- print ("Scanning whole repository" )
153- return None
154-
155- # List the files modified on current branch
156- if verbose :
157- print ("Scanning changes from current branch and any local changes" )
158- files_on_branch = rel_paths (
159- cmd_result_set (["git" , "diff" , "--name-only" , BASE_BRANCH ]), root_dir
160- )
161- ret |= files_on_branch
162- return ret
163- except :
164- # Git not available, run on entire repo
165- return None
166-
167-
16860def main ():
169- cmd_parser = argparse .ArgumentParser (description = "Auto-format C and Python files." )
170- cmd_parser .add_argument ("-c" , action = "store_true" , help = "Format C code only" )
171- cmd_parser .add_argument ("-p" , action = "store_true" , help = "Format Python code only" )
61+ cmd_parser = argparse .ArgumentParser (description = "Auto-format Python files." )
17262 cmd_parser .add_argument ("-v" , action = "store_true" , help = "Enable verbose output" )
17363 cmd_parser .add_argument (
174- "files " ,
175- nargs = "* " ,
176- help = "Run on specific globs. If not specied current branch changes will be used " ,
64+ "-f " ,
65+ action = "store_true " ,
66+ help = "Filter files provided on the command line against the default list of files to check. " ,
17767 )
68+ cmd_parser .add_argument ("files" , nargs = "*" , help = "Run on specific globs" )
17869 args = cmd_parser .parse_args ()
17970
180- # Setting only one of -c or -p disables the other. If both or neither are set, then do both.
181- format_c = args .c or not args .p
182- format_py = args .p or not args .c
183-
18471 # Expand the globs passed on the command line, or use the default globs above.
18572 files = []
18673 if args .files :
18774 files = list_files (args .files )
75+ if args .f :
76+ # Filter against the default list of files. This is a little fiddly
77+ # because we need to apply both the inclusion globs given in PATHS
78+ # as well as the EXCLUSIONS, and use absolute paths
79+ files = set (os .path .abspath (f ) for f in files )
80+ all_files = set (list_files (PATHS , EXCLUSIONS , TOP ))
81+ if args .v : # In verbose mode, log any files we're skipping
82+ for f in files - all_files :
83+ print ("Not checking: {}" .format (f ))
84+ files = list (files & all_files )
18885 else :
189- files = query_git_files (verbose = args .v )
190- if not files :
191- files = list_files (PATHS , EXCLUSIONS , TOP )
86+ files = list_files (PATHS , EXCLUSIONS , TOP )
19287
19388 # Extract files matching a specific language.
19489 def lang_files (exts ):
@@ -204,23 +99,13 @@ def batch(cmd, files, N=200):
20499 break
205100 subprocess .check_call (cmd + file_args )
206101
207- # Format C files with uncrustify.
208- if format_c :
209- command = ["uncrustify" , "-c" , UNCRUSTIFY_CFG , "-lC" , "--no-backup" ]
210- if not args .v :
211- command .append ("-q" )
212- batch (command , lang_files (C_EXTS ))
213- for file in lang_files (C_EXTS ):
214- fixup_c (file )
215-
216102 # Format Python files with black.
217- if format_py :
218- command = ["black" , "--fast" , "--line-length=99" ]
219- if args .v :
220- command .append ("-v" )
221- else :
222- command .append ("-q" )
223- batch (command , lang_files (PY_EXTS ))
103+ command = ["black" , "--fast" , "--line-length=99" ]
104+ if args .v :
105+ command .append ("-v" )
106+ else :
107+ command .append ("-q" )
108+ batch (command , lang_files (PY_EXTS ))
224109
225110
226111if __name__ == "__main__" :
0 commit comments