From 28fd17654e5468bc20007cc88b00b1b12eee3538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Atl=C3=A1ntico?= <49457785+Sarctiann@users.noreply.github.com> Date: Wed, 13 Jul 2022 01:40:54 -0300 Subject: [PATCH] tools: add a `v where fn join_path` command, for platform independent searching the location of a symbol in the local project, vlib, ~/.vmodules (#15014) --- cmd/tools/vbuild-tools.v | 2 +- cmd/tools/vwhere/finder.v | 171 ++++++++++++++++++++++++++ cmd/tools/vwhere/finder_utils.v | 205 ++++++++++++++++++++++++++++++++ cmd/tools/vwhere/vwhere.v | 9 ++ cmd/v/help/default.txt | 1 + cmd/v/help/where.txt | 42 +++++++ cmd/v/v.v | 1 + 7 files changed, 430 insertions(+), 1 deletion(-) create mode 100755 cmd/tools/vwhere/finder.v create mode 100755 cmd/tools/vwhere/finder_utils.v create mode 100755 cmd/tools/vwhere/vwhere.v create mode 100755 cmd/v/help/where.txt mode change 100644 => 100755 cmd/v/v.v diff --git a/cmd/tools/vbuild-tools.v b/cmd/tools/vbuild-tools.v index 3182cf0e48..2f4ce4355e 100644 --- a/cmd/tools/vbuild-tools.v +++ b/cmd/tools/vbuild-tools.v @@ -11,7 +11,7 @@ import v.util // should be compiled (v folder). // To implement that, these folders are initially skipped, then added // as a whole *after the testing.prepare_test_session call*. -const tools_in_subfolders = ['vdoc', 'vvet', 'vast'] +const tools_in_subfolders = ['vdoc', 'vvet', 'vast', 'vwhere'] // non_packaged_tools are tools that should not be packaged with // prebuild versions of V, to keep the size smaller. diff --git a/cmd/tools/vwhere/finder.v b/cmd/tools/vwhere/finder.v new file mode 100755 index 0000000000..dd83c61781 --- /dev/null +++ b/cmd/tools/vwhere/finder.v @@ -0,0 +1,171 @@ +module main + +import os +import term +import os.cmdline + +// Finder is entity that contains all the logic +struct Finder { +mut: + symbol Symbol + visib Visibility + mutab Mutability + name string + modul string + mth_of string + dirs []string + matches []Match +} + +fn (mut fdr Finder) configure_from_arguments() { + match args.len { + 1 { + fdr.name = args[0] + } + 2 { + fdr.symbol.set_from_str(args[0]) + fdr.name = args[1] + } + else { + fdr.symbol.set_from_str(args[0]) + fdr.name = args[1] + fdr.visib.set_from_str(cmdline.option(args, '-vis', '${Visibility.all}')) + fdr.mutab.set_from_str(cmdline.option(args, '-mut', '${Mutability.any}')) + if fdr.symbol != .var && fdr.mutab != .any { + make_and_print_error('-mut $fdr.mutab just can be setted with symbol_type:', + ['var'], '$fdr.symbol') + } + fdr.modul = cmdline.option(args, '-mod', '') + fdr.mth_of = cmdline.option(args, '-m-of', '') + if fdr.symbol !in [.@fn, .regexp] && fdr.mth_of != '' { + make_and_print_error('-m-of $fdr.mth_of just can be setted with symbol_types:', + ['fn', 'regexp'], '$fdr.symbol') + } + fdr.dirs = cmdline.options(args, '-dir') + } + } +} + +fn (mut fdr Finder) search_for_matches() { + // Define where search + mut recursive := true + mut paths_to_search := []string{} + if fdr.dirs.len == 0 && fdr.modul == '' { + paths_to_search << [current_dir, vmod_dir] + if vroot !in paths_to_search { + paths_to_search << vroot + } + paths_to_search << vmod_paths + } else if fdr.dirs.len == 0 && fdr.modul != '' { + paths_to_search << if fdr.modul == 'main' { current_dir } else { fdr.modul } + } else if fdr.dirs.len != 0 && fdr.modul == '' { + recursive = false + paths_to_search << fdr.dirs + } else { + recursive = false + paths_to_search << if fdr.modul == 'main' { current_dir } else { fdr.modul } + paths_to_search << fdr.dirs + } + mut files_to_search := []string{} + for path in paths_to_search { + files_to_search << collect_v_files(path, recursive) or { panic(err) } + } + + // Auxiliar rgx + sp := r'\s*' + op := r'\(' + cp := r'\)' + + // Build regex query + sy := '$fdr.symbol' + st := if fdr.mth_of != '' { '$sp$op$sp[a-z].*$sp$fdr.mth_of$cp$sp' } else { '.*' } + na := '$fdr.name' + + query := match fdr.symbol { + .@fn { + '.*$sy$st$na$sp${op}.*${cp}.*' + } + .var { + '.*$na$sp:=.*' + } + .@const { + '.*$na$sp=.*' + } + .regexp { + '$na' + } + else { + '.*$sy$sp$na${sp}.*' // for struct, enum and interface + } + } + // println(query) + is_const := fdr.symbol == .@const + for file in files_to_search { + n_line, line := search_within_file(file, query, is_const, fdr.visib, fdr.mutab) + if n_line != 0 { + fdr.matches << Match{file, n_line, line} + } + } +} + +fn (fdr Finder) show_results() { + if fdr.matches.len < 1 && (verbose || header) { + print(fdr) + println(maybe_color(term.bright_yellow, 'No Matches found')) + } else if verbose || header { + print(fdr) + println(maybe_color(term.bright_green, '$fdr.matches.len matches Found\n')) + for result in fdr.matches { + result.show() + } + } else { + for result in fdr.matches { + result.show() + } + } +} + +fn (fdr Finder) str() string { + v := maybe_color(term.bright_red, '$fdr.visib') + m := maybe_color(term.bright_red, '$fdr.mutab') + st := if fdr.mth_of != '' { ' ( _ $fdr.mth_of)' } else { '' } + s := maybe_color(term.bright_magenta, '$fdr.symbol') + n := maybe_color(term.bright_cyan, '$fdr.name') + + mm := if fdr.modul != '' { maybe_color(term.blue, '$fdr.modul') } else { '' } + dd := if fdr.dirs.len != 0 { + fdr.dirs.map(maybe_color(term.blue, it)) + } else { + fdr.dirs + } + + dm := if fdr.dirs.len == 0 && fdr.modul == '' { + 'all the project scope' + } else if fdr.dirs.len == 0 && fdr.modul != '' { + 'module $mm' + } else if fdr.dirs.len != 0 && fdr.modul == '' { + 'directories: $dd' + } else { + 'module $mm searching within directories: $dd' + } + + return '\nFind: $s$st $n | visibility: $v mutability: $m\nwithin $dm ' +} + +// Match is one result of the search_for_matches() process +struct Match { + path string [required] + line int [required] + text string [required] +} + +fn (mtc Match) show() { + path := maybe_color(term.bright_magenta, mtc.path) + line := maybe_color(term.bright_yellow, '$mtc.line') + text := maybe_color(term.bright_green, '$mtc.text') + if verbose || format { + println('$path\n$line : [ $text ]\n') + } else { + println('$path:$line: $text') + } +} diff --git a/cmd/tools/vwhere/finder_utils.v b/cmd/tools/vwhere/finder_utils.v new file mode 100755 index 0000000000..ff1305a0a6 --- /dev/null +++ b/cmd/tools/vwhere/finder_utils.v @@ -0,0 +1,205 @@ +module main + +import os +import term +import regex +import v.pref +import os.cmdline + +// Symbol type to search +enum Symbol { + @fn + @struct + @interface + @enum + @const + var + regexp +} + +// Visibility of the symbols to search +enum Visibility { + all + @pub + pri +} + +// Mutability of the symbols to search +enum Mutability { + any + yes + not +} + +const ( + args = os.args[2..] + verbose = '-v' in cmdline.only_options(args) + header = '-h' in cmdline.only_options(args) + format = '-f' in cmdline.only_options(args) + symbols = { + 'fn': Symbol.@fn + 'struct': .@struct + 'interface': .@interface + 'enum': .@enum + 'const': .@const + 'var': .var + 'regexp': .regexp + } + visibilities = { + 'all': Visibility.all + 'pub': .@pub + 'pri': .pri + } + mutabilities = { + 'any': Mutability.any + 'yes': .yes + 'not': .not + } + vexe = pref.vexe_path() + vroot = os.dir(vexe) + vmod_dir = os.vmodules_dir() + vmod_paths = os.vmodules_paths()[1..] + current_dir = os.abs_path('.') + color_out = term.can_show_color_on_stdout() +) + +fn (mut cfg Symbol) set_from_str(str_in string) { + if str_in !in symbols { + invalid_option(cfg, str_in) + } + cfg = symbols[str_in] +} + +fn (mut cfg Visibility) set_from_str(str_in string) { + if str_in !in visibilities { + invalid_option(cfg, str_in) + } + cfg = visibilities[str_in] +} + +fn (mut cfg Mutability) set_from_str(str_in string) { + if str_in !in mutabilities { + invalid_option(cfg, str_in) + } + cfg = mutabilities[str_in] +} + +type ParamOption = Mutability | Symbol | Visibility + +// General helpers +fn invalid_option(invalid ParamOption, arg string) { + match invalid.type_name() { + 'Symbol' { + msg := 'First arg (symbol type) must be one of the following:' + make_and_print_error(msg, symbols.keys(), arg) + } + 'Visibility' { + msg := '"-vis" (visibility) must be one of the following:' + make_and_print_error(msg, visibilities.keys(), arg) + } + 'Mutability' { + msg := '"-mut" (mutability) must be one of the following:' + make_and_print_error(msg, mutabilities.keys(), arg) + } + else { + exit(1) + } + } +} + +fn valid_args_quantity_or_show_help() { + if true in [ + args.len < 1, + '-help' in os.args, + '--help' in os.args, + os.args[1..] == ['where', 'help'], + ] { + os.system('${os.quoted_path(vexe)} help where') + exit(0) + } +} + +fn make_and_print_error(msg string, opts []string, arg string) { + eprintln('\n' + maybe_color(term.bright_yellow, msg)) + if opts.len > 0 { + eprint(opts.map(maybe_color(term.bright_green, it)).join(' | ')) + } + eprintln(' ...can not be ${maybe_color(term.bright_red, arg)}') + exit(1) +} + +fn maybe_color(term_color fn (string) string, str string) string { + if color_out { + return term_color(str) + } else { + return str + } +} + +fn collect_v_files(path string, recursive bool) ?[]string { + if path.len == 0 { + return error('path cannot be empty') + } + if !os.is_dir(path) { + return error('path does not exist or is not a directory') + } + mut all_files := []string{} + mut entries := os.ls(path)? + mut local_path_separator := os.path_separator + if path.ends_with(os.path_separator) { + local_path_separator = '' + } + for entry in entries { + file := path + local_path_separator + entry + if os.is_dir(file) && !os.is_link(file) && recursive { + all_files << collect_v_files(file, recursive)? + } else if os.exists(file) && (file.ends_with('.v') || file.ends_with('.vsh')) { + all_files << file + } + } + return all_files +} + +fn search_within_file(file string, query string, is_const bool, v Visibility, m Mutability) (int, string) { + mut re := regex.regex_opt(query) or { panic(err) } + lines := os.read_lines(file) or { panic(err) } + mut const_found := if is_const { false } else { true } + mut n_line := 1 + for line in lines { + if line.starts_with('const') { + const_found = true + } + if re.matches_string(line) && const_found { + words := line.split(' ').map(it.trim('\t')) + match v { + .all {} + .@pub { + if 'pub' !in words { + continue + } + } + .pri { + if 'pub' in words { + continue + } + } + } + match m { + .any {} + .yes { + if 'mut' !in words { + continue + } + } + .not { + if 'mut' in words { + continue + } + } + } + return n_line, line.replace(' {', '') + } + n_line++ + } + return 0, '' +} diff --git a/cmd/tools/vwhere/vwhere.v b/cmd/tools/vwhere/vwhere.v new file mode 100755 index 0000000000..e362c93192 --- /dev/null +++ b/cmd/tools/vwhere/vwhere.v @@ -0,0 +1,9 @@ +module main + +fn main() { + valid_args_quantity_or_show_help() + mut fdr := Finder{} + fdr.configure_from_arguments() + fdr.search_for_matches() + fdr.show_results() +} diff --git a/cmd/v/help/default.txt b/cmd/v/help/default.txt index eb0880d2bd..65b1240a6d 100644 --- a/cmd/v/help/default.txt +++ b/cmd/v/help/default.txt @@ -32,6 +32,7 @@ V supports the following commands: vlib-docs Generate and open the documentation of all the vlib modules. repl Run the REPL. watch Re-compile/re-run a source file, each time it is changed. + where Find and print the location of current project declarations. * Installation/self updating: symlink Create a symbolic link for V. diff --git a/cmd/v/help/where.txt b/cmd/v/help/where.txt new file mode 100755 index 0000000000..6219250524 --- /dev/null +++ b/cmd/v/help/where.txt @@ -0,0 +1,42 @@ +Usage: + v where [symbol_type] [symbol_name] [params] + +Examples: + v where fn main + v where struct User + v where fn say_hello -mod Person + v where fn area -m-of Square + v where interface callable -dir some -dir other + +-------------------------------------------------------------------------------- +Prints the location of the searched symbols in the scope of the current project. +-------------------------------------------------------------------------------- + +symbol_name can be: + fn (by default if omit [symbol_type]) + struct + interface + enum + const + var + regexp + +params: + -mod [mod_name] Restrict to search recursively only within of the given + module, if not provided search in entire v scope. + -dir [dir_path] Restrict to search non recursively within the given + folder/s, if not provided, search in entire v scope. + -vis [visibility] Can be: all, pub, pri (all by default if not provided). + Restrict to search symbols with the given visibility. + -mut [mutability] Can be: any, yes, not (any by default if not provided). + Restrict to search symbols with the given mutability. + -m-of [struct_name] (just for fn symbol_name). + Restrict to search fn as method of the given struct. +flags: + -h Include header + -f Format output (each match uses 3 lines) + -v For both above + +Note: + This tool is inspired by the vdoc tool and its design. However, this is + for a more specific use, and can be improved. diff --git a/cmd/v/v.v b/cmd/v/v.v old mode 100644 new mode 100755 index 77c019b0aa..cc5630afd9 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -48,6 +48,7 @@ const ( 'vet', 'wipe-cache', 'watch', + 'where', ] list_of_flags_that_allow_duplicates = ['cc', 'd', 'define', 'cf', 'cflags'] )