mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
318 lines
9.7 KiB
V
318 lines
9.7 KiB
V
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
module main
|
|
|
|
import os
|
|
import os.cmdline
|
|
import rand
|
|
import term
|
|
import v.ast
|
|
import v.pref
|
|
import v.fmt
|
|
import v.util
|
|
import v.util.diff
|
|
import v.parser
|
|
import v.help
|
|
|
|
struct FormatOptions {
|
|
is_l bool
|
|
is_c bool // Note: This refers to the '-c' fmt flag, NOT the C backend
|
|
is_w bool
|
|
is_diff bool
|
|
is_verbose bool
|
|
is_debug bool
|
|
is_noerror bool
|
|
is_verify bool // exit(1) if the file is not vfmt'ed
|
|
is_worker bool // true *only* in the worker processes. Note: workers can crash.
|
|
is_backup bool // make a `file.v.bak` copy *before* overwriting a `file.v` in place with `-w`
|
|
in_process bool // do not fork a worker process; potentially faster, but more prone to crashes for invalid files
|
|
mut:
|
|
diff_cmd string // filled in when -diff or -verify is passed
|
|
}
|
|
|
|
const formatted_file_token = '\@\@\@' + 'FORMATTED_FILE: '
|
|
const vtmp_folder = os.vtmp_dir()
|
|
const term_colors = term.can_show_color_on_stderr()
|
|
|
|
fn main() {
|
|
// if os.getenv('VFMT_ENABLE') == '' {
|
|
// eprintln('v fmt is disabled for now')
|
|
// exit(1)
|
|
// }
|
|
toolexe := os.executable()
|
|
util.set_vroot_folder(os.dir(os.dir(os.dir(toolexe))))
|
|
args := util.join_env_vflags_and_os_args()
|
|
mut foptions := FormatOptions{
|
|
is_c: '-c' in args
|
|
is_l: '-l' in args
|
|
is_w: '-w' in args
|
|
is_diff: '-diff' in args
|
|
is_verbose: '-verbose' in args || '--verbose' in args
|
|
is_worker: '-worker' in args
|
|
is_debug: '-debug' in args
|
|
is_noerror: '-noerror' in args
|
|
is_verify: '-verify' in args
|
|
is_backup: '-backup' in args
|
|
in_process: '-inprocess' in args
|
|
}
|
|
if term_colors {
|
|
os.setenv('VCOLORS', 'always', true)
|
|
}
|
|
foptions.vlog('vfmt foptions: ${foptions}')
|
|
if foptions.is_worker {
|
|
// -worker should be added by a parent vfmt process.
|
|
// We launch a sub process for each file because
|
|
// the v compiler can do an early exit if it detects
|
|
// a syntax error, but we want to process ALL passed
|
|
// files if possible.
|
|
foptions.format_file(cmdline.option(args, '-worker', ''))
|
|
exit(0)
|
|
}
|
|
// we are NOT a worker at this stage, i.e. we are a parent vfmt process
|
|
possible_files := cmdline.only_non_options(cmdline.options_after(args, ['fmt']))
|
|
if foptions.is_verbose {
|
|
eprintln('vfmt toolexe: ${toolexe}')
|
|
eprintln('vfmt args: ' + os.args.str())
|
|
eprintln('vfmt env_vflags_and_os_args: ' + args.str())
|
|
eprintln('vfmt possible_files: ' + possible_files.str())
|
|
}
|
|
files := util.find_all_v_files(possible_files) or {
|
|
verror(err.msg())
|
|
return
|
|
}
|
|
if os.is_atty(0) == 0 && files.len == 0 {
|
|
foptions.format_pipe()
|
|
exit(0)
|
|
}
|
|
if files.len == 0 || '-help' in args || '--help' in args {
|
|
help.print_and_exit('fmt')
|
|
}
|
|
mut cli_args_no_files := []string{}
|
|
for idx, a in os.args {
|
|
if idx == 0 {
|
|
cli_args_no_files << os.quoted_path(a)
|
|
continue
|
|
}
|
|
if a !in files {
|
|
cli_args_no_files << a
|
|
}
|
|
}
|
|
mut errors := 0
|
|
mut has_internal_error := false
|
|
mut prefs := setup_preferences()
|
|
for file in files {
|
|
fpath := os.real_path(file)
|
|
if foptions.is_verify && foptions.in_process {
|
|
// For a small amount of files, it is faster to process
|
|
// everything directly in the same process, single threaded,
|
|
// when vfmt is compiled with `-gc none`:
|
|
if !foptions.verify_file(prefs, fpath) {
|
|
println("${file} is not vfmt'ed")
|
|
errors++
|
|
}
|
|
continue
|
|
}
|
|
mut worker_command_array := cli_args_no_files.clone()
|
|
worker_command_array << ['-worker', util.quote_path(fpath)]
|
|
worker_cmd := worker_command_array.join(' ')
|
|
foptions.vlog('vfmt worker_cmd: ${worker_cmd}')
|
|
worker_result := os.execute(worker_cmd)
|
|
// Guard against a possibly crashing worker process.
|
|
if worker_result.exit_code != 0 {
|
|
eprintln(worker_result.output)
|
|
if worker_result.exit_code == 1 {
|
|
eprintln('Internal vfmt error while formatting file: ${file}.')
|
|
has_internal_error = true
|
|
continue
|
|
}
|
|
errors++
|
|
continue
|
|
}
|
|
if worker_result.output.len > 0 {
|
|
if worker_result.output.contains(formatted_file_token) {
|
|
wresult := worker_result.output.split(formatted_file_token)
|
|
formatted_warn_errs := wresult[0]
|
|
formatted_file_path := wresult[1].trim_right('\n\r')
|
|
foptions.post_process_file(fpath, formatted_file_path) or { errors = errors + 1 }
|
|
if formatted_warn_errs.len > 0 {
|
|
eprintln(formatted_warn_errs)
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
errors++
|
|
}
|
|
ecode := if has_internal_error { 5 } else { 0 }
|
|
if errors > 0 {
|
|
if !foptions.is_diff {
|
|
eprintln('Encountered a total of: ${errors} formatting errors.')
|
|
}
|
|
match true {
|
|
foptions.is_noerror { exit(0 + ecode) }
|
|
foptions.is_verify { exit(1 + ecode) }
|
|
foptions.is_c { exit(2 + ecode) }
|
|
else { exit(1 + ecode) }
|
|
}
|
|
}
|
|
exit(ecode)
|
|
}
|
|
|
|
fn (foptions &FormatOptions) verify_file(prefs &pref.Preferences, fpath string) bool {
|
|
fcontent := foptions.formated_content_from_file(prefs, fpath)
|
|
content := os.read_file(fpath) or { return false }
|
|
return fcontent == content
|
|
}
|
|
|
|
fn setup_preferences() &pref.Preferences {
|
|
mut prefs := pref.new_preferences()
|
|
prefs.is_fmt = true
|
|
prefs.skip_warnings = true
|
|
return prefs
|
|
}
|
|
|
|
fn setup_preferences_and_table() (&pref.Preferences, &ast.Table) {
|
|
return setup_preferences(), ast.new_table()
|
|
}
|
|
|
|
fn (foptions &FormatOptions) vlog(msg string) {
|
|
if foptions.is_verbose {
|
|
eprintln(msg)
|
|
}
|
|
}
|
|
|
|
fn (foptions &FormatOptions) formated_content_from_file(prefs &pref.Preferences, file string) string {
|
|
mut table := ast.new_table()
|
|
file_ast := parser.parse_file(file, mut table, .parse_comments, prefs)
|
|
formated_content := fmt.fmt(file_ast, mut table, prefs, foptions.is_debug)
|
|
return formated_content
|
|
}
|
|
|
|
fn (foptions &FormatOptions) format_file(file string) {
|
|
file_name := os.file_name(file)
|
|
ulid := rand.ulid()
|
|
vfmt_output_path := os.join_path(vtmp_folder, 'vfmt_${ulid}_${file_name}')
|
|
if file.contains('_vfmt_off') {
|
|
os.cp(file, vfmt_output_path) or { panic(err) }
|
|
foptions.vlog('format_file copied the file ${file} as it was, 1:1, since its name contains `_vfmt_off`.')
|
|
eprintln('${formatted_file_token}${vfmt_output_path}')
|
|
return
|
|
}
|
|
foptions.vlog('vfmt2 running fmt.fmt over file: ${file}')
|
|
prefs, mut table := setup_preferences_and_table()
|
|
file_ast := parser.parse_file(file, mut table, .parse_comments, prefs)
|
|
// checker.new_checker(table, prefs).check(file_ast)
|
|
formatted_content := fmt.fmt(file_ast, mut table, prefs, foptions.is_debug)
|
|
os.write_file(vfmt_output_path, formatted_content) or { panic(err) }
|
|
foptions.vlog('fmt.fmt worked and ${formatted_content.len} bytes were written to ${vfmt_output_path} .')
|
|
eprintln('${formatted_file_token}${vfmt_output_path}')
|
|
}
|
|
|
|
fn (foptions &FormatOptions) format_pipe() {
|
|
foptions.vlog('vfmt2 running fmt.fmt over stdin')
|
|
prefs, mut table := setup_preferences_and_table()
|
|
input_text := os.get_raw_lines_joined()
|
|
file_ast := parser.parse_text(input_text, '', mut table, .parse_comments, prefs)
|
|
// checker.new_checker(table, prefs).check(file_ast)
|
|
formatted_content := fmt.fmt(file_ast, mut table, prefs, foptions.is_debug,
|
|
source_text: input_text
|
|
)
|
|
print(formatted_content)
|
|
flush_stdout()
|
|
foptions.vlog('fmt.fmt worked and ${formatted_content.len} bytes were written to stdout.')
|
|
}
|
|
|
|
fn print_compiler_options(compiler_params &pref.Preferences) {
|
|
eprintln(' os: ' + compiler_params.os.str())
|
|
eprintln(' ccompiler: ${compiler_params.ccompiler}')
|
|
eprintln(' path: ${compiler_params.path} ')
|
|
eprintln(' out_name: ${compiler_params.out_name} ')
|
|
eprintln(' vroot: ${compiler_params.vroot} ')
|
|
eprintln('lookup_path: ${compiler_params.lookup_path} ')
|
|
eprintln(' out_name: ${compiler_params.out_name} ')
|
|
eprintln(' cflags: ${compiler_params.cflags} ')
|
|
eprintln(' is_test: ${compiler_params.is_test} ')
|
|
eprintln(' is_script: ${compiler_params.is_script} ')
|
|
}
|
|
|
|
fn (mut foptions FormatOptions) post_process_file(file string, formatted_file_path string) ! {
|
|
if formatted_file_path == '' {
|
|
return
|
|
}
|
|
fc := os.read_file(file) or {
|
|
eprintln('File ${file} could not be read')
|
|
return
|
|
}
|
|
formatted_fc := os.read_file(formatted_file_path) or {
|
|
eprintln('File ${formatted_file_path} could not be read')
|
|
return
|
|
}
|
|
is_formatted_different := fc != formatted_fc
|
|
if foptions.is_diff {
|
|
if !is_formatted_different {
|
|
return
|
|
}
|
|
println(diff.compare_files(file, formatted_file_path)!)
|
|
return error('')
|
|
}
|
|
if foptions.is_verify {
|
|
if !is_formatted_different {
|
|
return
|
|
}
|
|
println("${file} is not vfmt'ed")
|
|
return error('')
|
|
}
|
|
if foptions.is_c {
|
|
if is_formatted_different {
|
|
eprintln('File is not formatted: ${file}')
|
|
return error('')
|
|
}
|
|
return
|
|
}
|
|
if foptions.is_l {
|
|
if is_formatted_different {
|
|
eprintln('File needs formatting: ${file}')
|
|
}
|
|
return
|
|
}
|
|
if foptions.is_w {
|
|
if is_formatted_different {
|
|
if foptions.is_backup {
|
|
file_bak := '${file}.bak'
|
|
os.cp(file, file_bak) or {}
|
|
}
|
|
mut perms_to_restore := u32(0)
|
|
$if !windows {
|
|
fm := os.inode(file)
|
|
perms_to_restore = fm.bitmask()
|
|
}
|
|
os.mv_by_cp(formatted_file_path, file) or { panic(err) }
|
|
$if !windows {
|
|
os.chmod(file, int(perms_to_restore)) or { panic(err) }
|
|
}
|
|
eprintln('Reformatted file: ${file}')
|
|
} else {
|
|
eprintln('Already formatted file: ${file}')
|
|
}
|
|
return
|
|
}
|
|
print(formatted_fc)
|
|
flush_stdout()
|
|
}
|
|
|
|
fn read_source_lines(file string) ![]string {
|
|
source_lines := os.read_lines(file) or { return error('can not read ${file}') }
|
|
return source_lines
|
|
}
|
|
|
|
@[noreturn]
|
|
fn verror(s string) {
|
|
util.verror('vfmt error', s)
|
|
}
|
|
|
|
fn (f FormatOptions) str() string {
|
|
return
|
|
'FormatOptions{ is_l: ${f.is_l}, is_w: ${f.is_w}, is_diff: ${f.is_diff}, is_verbose: ${f.is_verbose},' +
|
|
' is_worker: ${f.is_worker}, is_debug: ${f.is_debug}, is_noerror: ${f.is_noerror},' +
|
|
' is_verify: ${f.is_verify}" }'
|
|
}
|