1// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. 2// Use of this source code is governed by an MIT license 3// that can be found in the LICENSE file. 4module main 5 6import os 7import os.cmdline 8import v.ast 9import v.pref 10import v.fmt 11import v.util 12import v.parser 13import v.table 14import vhelp 15 16struct FormatOptions { 17 is_l bool 18 is_c bool // NB: This refers to the '-c' fmt flag, NOT the C backend 19 is_w bool 20 is_diff bool 21 is_verbose bool 22 is_all bool 23 is_worker bool 24 is_debug bool 25 is_noerror bool 26 is_verify bool // exit(1) if the file is not vfmt'ed 27} 28 29const ( 30 formatted_file_token = '\@\@\@' + 'FORMATTED_FILE: ' 31 platform_and_file_extensions = [ 32 ['windows', '_windows.v'], 33 ['linux', '_lin.v', '_linux.v', '_nix.v'], 34 ['macos', '_mac.v', '_darwin.v'], 35 ['freebsd', '_bsd.v', '_freebsd.v'], 36 ['netbsd', '_bsd.v', '_netbsd.v'], 37 ['openbsd', '_bsd.v', '_openbsd.v'], 38 ['solaris', '_solaris.v'], 39 ['haiku', '_haiku.v'], 40 ['qnx', '_qnx.v'] 41 ] 42) 43 44fn main() { 45 // if os.getenv('VFMT_ENABLE') == '' { 46 // eprintln('v fmt is disabled for now') 47 // exit(1) 48 // } 49 toolexe := os.executable() 50 util.set_vroot_folder(os.dir(os.dir(os.dir(toolexe)))) 51 args := util.join_env_vflags_and_os_args() 52 foptions := FormatOptions{ 53 is_c: '-c' in args 54 is_l: '-l' in args 55 is_w: '-w' in args 56 is_diff: '-diff' in args 57 is_verbose: '-verbose' in args || '--verbose' in args 58 is_all: '-all' in args || '--all' in args 59 is_worker: '-worker' in args 60 is_debug: '-debug' in args 61 is_noerror: '-noerror' in args 62 is_verify: '-verify' in args 63 } 64 if foptions.is_verbose { 65 eprintln('vfmt foptions: $foptions') 66 } 67 if foptions.is_worker { 68 // -worker should be added by a parent vfmt process. 69 // We launch a sub process for each file because 70 // the v compiler can do an early exit if it detects 71 // a syntax error, but we want to process ALL passed 72 // files if possible. 73 foptions.format_file(cmdline.option(args, '-worker', '')) 74 exit(0) 75 } 76 // we are NOT a worker at this stage, i.e. we are a parent vfmt process 77 possible_files := cmdline.only_non_options(cmdline.options_after(args, ['fmt'])) 78 if foptions.is_verbose { 79 eprintln('vfmt toolexe: $toolexe') 80 eprintln('vfmt args: ' + os.args.str()) 81 eprintln('vfmt env_vflags_and_os_args: ' + args.str()) 82 eprintln('vfmt possible_files: ' + possible_files.str()) 83 } 84 mut files := []string{} 85 for file in possible_files { 86 if !file.ends_with('.v') && !file.ends_with('.vv') { 87 verror('v fmt can only be used on .v files.\nOffending file: "$file"') 88 continue 89 } 90 if !os.exists(file) { 91 verror('"$file" does not exist') 92 continue 93 } 94 files << file 95 } 96 if files.len == 0 { 97 vhelp.show_topic('fmt') 98 exit(0) 99 } 100 mut cli_args_no_files := []string{} 101 for a in os.args { 102 if a !in files { 103 cli_args_no_files << a 104 } 105 } 106 mut errors := 0 107 for file in files { 108 fpath := os.real_path(file) 109 mut worker_command_array := cli_args_no_files.clone() 110 worker_command_array << ['-worker', util.quote_path_with_spaces(fpath)] 111 worker_cmd := worker_command_array.join(' ') 112 if foptions.is_verbose { 113 eprintln('vfmt worker_cmd: $worker_cmd') 114 } 115 worker_result := os.exec(worker_cmd) or { 116 errors++ 117 continue 118 } 119 if worker_result.exit_code != 0 { 120 eprintln(worker_result.output) 121 if worker_result.exit_code == 1 { 122 eprintln('vfmt error while formatting file: $file .') 123 } 124 errors++ 125 continue 126 } 127 if worker_result.output.len > 0 { 128 if worker_result.output.contains(formatted_file_token) { 129 wresult := worker_result.output.split(formatted_file_token) 130 formatted_warn_errs := wresult[0] 131 formatted_file_path := wresult[1].trim_right('\n\r') 132 foptions.post_process_file(fpath, formatted_file_path) 133 if formatted_warn_errs.len > 0 { 134 eprintln(formatted_warn_errs) 135 } 136 continue 137 } 138 } 139 errors++ 140 } 141 if errors > 0 { 142 eprintln('Encountered a total of: $errors errors.') 143 if foptions.is_noerror { 144 exit(0) 145 } 146 exit(1) 147 } 148} 149 150fn (foptions &FormatOptions) format_file(file string) { 151 mut prefs := pref.new_preferences() 152 prefs.is_fmt = true 153 if foptions.is_verbose { 154 eprintln('vfmt2 running fmt.fmt over file: $file') 155 } 156 table := table.new_table() 157 // checker := checker.new_checker(table, prefs) 158 file_ast := parser.parse_file(file, table, .parse_comments, prefs, &ast.Scope{ 159 parent: 0 160 }) 161 // checker.check(file_ast) 162 formatted_content := fmt.fmt(file_ast, table, foptions.is_debug) 163 file_name := os.file_name(file) 164 vfmt_output_path := os.join_path(os.temp_dir(), 'vfmt_' + file_name) 165 os.write_file(vfmt_output_path, formatted_content) 166 if foptions.is_verbose { 167 eprintln('fmt.fmt worked and $formatted_content.len bytes were written to $vfmt_output_path .') 168 } 169 eprintln('$formatted_file_token$vfmt_output_path') 170} 171 172fn print_compiler_options(compiler_params &pref.Preferences) { 173 eprintln(' os: ' + compiler_params.os.str()) 174 eprintln(' ccompiler: $compiler_params.ccompiler') 175 eprintln(' path: $compiler_params.path ') 176 eprintln(' out_name: $compiler_params.out_name ') 177 eprintln(' vroot: $compiler_params.vroot ') 178 eprintln('lookup_path: $compiler_params.lookup_path ') 179 eprintln(' out_name: $compiler_params.out_name ') 180 eprintln(' cflags: $compiler_params.cflags ') 181 eprintln(' is_test: $compiler_params.is_test ') 182 eprintln(' is_script: $compiler_params.is_script ') 183} 184 185fn (foptions &FormatOptions) post_process_file(file, formatted_file_path string) { 186 if formatted_file_path.len == 0 { 187 return 188 } 189 if foptions.is_diff { 190 diff_cmd := util.find_working_diff_command() or { 191 eprintln(err) 192 return 193 } 194 if foptions.is_verbose { 195 eprintln('Using diff command: $diff_cmd') 196 } 197 println(util.color_compare_files(diff_cmd, file, formatted_file_path)) 198 return 199 } 200 if foptions.is_verify { 201 diff_cmd := util.find_working_diff_command() or { 202 eprintln(err) 203 return 204 } 205 x := util.color_compare_files(diff_cmd, file, formatted_file_path) 206 if x.len != 0 { 207 println("$file is not vfmt'ed") 208 exit(1) 209 } 210 return 211 } 212 fc := os.read_file(file) or { 213 eprintln('File $file could not be read') 214 return 215 } 216 formatted_fc := os.read_file(formatted_file_path) or { 217 eprintln('File $formatted_file_path could not be read') 218 return 219 } 220 is_formatted_different := fc != formatted_fc 221 if foptions.is_c { 222 if is_formatted_different { 223 eprintln('File is not formatted: $file') 224 exit(2) 225 } 226 return 227 } 228 if foptions.is_l { 229 if is_formatted_different { 230 eprintln('File needs formatting: $file') 231 } 232 return 233 } 234 if foptions.is_w { 235 if is_formatted_different { 236 os.mv_by_cp(formatted_file_path, file) or { 237 panic(err) 238 } 239 eprintln('Reformatted file: $file') 240 } else { 241 eprintln('Already formatted file: $file') 242 } 243 return 244 } 245 print(formatted_fc) 246} 247 248fn (f FormatOptions) str() string { 249 return 'FormatOptions{ is_l: $f.is_l, is_w: $f.is_w, is_diff: $f.is_diff, is_verbose: $f.is_verbose,' + 250 ' is_all: $f.is_all, is_worker: $f.is_worker, is_debug: $f.is_debug, is_noerror: $f.is_noerror,' + 251 ' is_verify: $f.is_verify" }' 252} 253 254fn file_to_target_os(file string) string { 255 for extensions in platform_and_file_extensions { 256 for ext in extensions { 257 if file.ends_with(ext) { 258 return extensions[0] 259 } 260 } 261 } 262 return '' 263} 264 265fn file_to_mod_name_and_is_module_file(file string) (string, bool) { 266 mut mod_name := 'main' 267 mut is_module_file := false 268 flines := read_source_lines(file) or { 269 return mod_name, is_module_file 270 } 271 for fline in flines { 272 line := fline.trim_space() 273 if line.starts_with('module ') { 274 if !line.starts_with('module main') { 275 is_module_file = true 276 mod_name = line.replace('module ', ' ').trim_space() 277 } 278 break 279 } 280 } 281 return mod_name, is_module_file 282} 283 284fn read_source_lines(file string) ?[]string { 285 source_lines := os.read_lines(file) or { 286 return error('can not read $file') 287 } 288 return source_lines 289} 290 291fn get_compile_name_of_potential_v_project(file string) string { 292 // This function get_compile_name_of_potential_v_project returns: 293 // a) the file's folder, if file is part of a v project 294 // b) the file itself, if the file is a standalone v program 295 pfolder := os.real_path(os.dir(file)) 296 // a .v project has many 'module main' files in one folder 297 // if there is only one .v file, then it must be a standalone 298 all_files_in_pfolder := os.ls(pfolder) or { 299 panic(err) 300 } 301 mut vfiles := []string{} 302 for f in all_files_in_pfolder { 303 vf := os.join_path(pfolder, f) 304 if f.starts_with('.') || !f.ends_with('.v') || os.is_dir(vf) { 305 continue 306 } 307 vfiles << vf 308 } 309 if vfiles.len == 1 { 310 return file 311 } 312 // ///////////////////////////////////////////////////////////// 313 // At this point, we know there are many .v files in the folder 314 // We will have to read them all, and if there are more than one 315 // containing `fn main` then the folder contains multiple standalone 316 // v programs. If only one contains `fn main` then the folder is 317 // a project folder, that should be compiled with `v pfolder`. 318 mut main_fns := 0 319 for f in vfiles { 320 slines := read_source_lines(f) or { 321 panic(err) 322 } 323 for line in slines { 324 if line.contains('fn main()') { 325 main_fns++ 326 if main_fns > 1 { 327 return file 328 } 329 } 330 } 331 } 332 return pfolder 333} 334 335fn verror(s string) { 336 util.verror('vfmt error', s) 337} 338