-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
635 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
module cli | ||
|
||
import os | ||
import shy.vxt | ||
|
||
pub const ( | ||
exe_version = version() | ||
exe_name = os.file_name(os.executable()) | ||
exe_short_name = os.file_name(os.executable()).replace('.exe', '') | ||
exe_dir = os.dir(os.real_path(os.executable())) | ||
exe_args_description = 'input | ||
or: shy <sub-command> [options] input' | ||
exe_description = 'shy is a module and tool made with love. | ||
It is primarily aimed at V developers roaming the creative corners of coding. | ||
shy can compile, package and deploy V apps for a wide range of platforms like: | ||
Linux, macOS, Windows, Android and HTML5 (WASM). | ||
The following does the same as if they were passed to the "v" compiler: | ||
Flags: | ||
-autofree, -gc <type>, -g, -cg, -prod, -showcc | ||
Sub-commands: | ||
run Run the V code | ||
doctor Display useful info about your system for bug reports' | ||
exe_git_hash = shy_commit_hash() | ||
work_directory = shy_tmp_work_dir() | ||
cache_directory = shy_cache_dir() | ||
rip_vflags = ['-autofree', '-gc', '-g', '-cg', '-prod', 'run', '-showcc'] | ||
subcmds = ['complete', 'test-cleancode'] | ||
accepted_input_files = ['.v'] | ||
) | ||
|
||
pub const shy_env_vars = [ | ||
'SHY_FLAGS', | ||
'VEXE', | ||
'VMODULES', | ||
] | ||
|
||
// check_essentials ensures that the work environment has all needed dependencies | ||
// and meet all required needs. | ||
pub fn check_essentials(exit_on_error bool) { | ||
// Validate V install | ||
if vxt.vexe() == '' { | ||
eprintln('No V install could be detected') | ||
eprintln('Please install V from https://github.com/vlang/v') | ||
eprintln('or provide a valid path to V via VEXE env variable') | ||
if exit_on_error { | ||
exit(1) | ||
} | ||
} | ||
} | ||
|
||
// dot_shy_path returns the path to the `.shy` file next to `file_or_dir_path` if found, an empty string otherwise. | ||
pub fn dot_shy_path(file_or_dir_path string) string { | ||
if os.is_dir(file_or_dir_path) { | ||
if os.is_file(os.join_path(file_or_dir_path, '.shy')) { | ||
return os.join_path(file_or_dir_path, '.shy') | ||
} | ||
} else { | ||
if os.is_file(os.join_path(os.dir(file_or_dir_path), '.shy')) { | ||
return os.join_path(os.dir(file_or_dir_path), '.shy') | ||
} | ||
} | ||
return '' | ||
} | ||
|
||
// launch_cmd launches an external command. | ||
pub fn launch_cmd(args []string) ! { | ||
mut cmd := args[0] | ||
tool_args := args[1..] | ||
if cmd.starts_with('test-') { | ||
cmd = cmd.all_after('test-') | ||
} | ||
v := vxt.vexe() | ||
tool_exe := os.join_path(cli.exe_dir, 'cmd', cmd) | ||
if os.is_executable(v) { | ||
hash_file := os.join_path(cli.exe_dir, 'cmd', '.' + cmd + '.hash') | ||
|
||
mut hash := '' | ||
if os.is_file(hash_file) { | ||
hash = os.read_file(hash_file) or { '' } | ||
} | ||
if hash != cli.exe_git_hash { | ||
v_cmd := [ | ||
v, | ||
tool_exe + '.v', | ||
'-o', | ||
tool_exe, | ||
] | ||
res := os.execute(v_cmd.join(' ')) | ||
if res.exit_code < 0 { | ||
return error('${@MOD}.${@FN} failed compiling "$cmd": $res.output') | ||
} | ||
if res.exit_code == 0 { | ||
os.write_file(hash_file, cli.exe_git_hash) or {} | ||
} else { | ||
vcmd := v_cmd.join(' ') | ||
return error('${@MOD}.${@FN} "$vcmd" failed:\n$res.output') | ||
} | ||
} | ||
} | ||
if os.is_executable(tool_exe) { | ||
os.setenv('SHY_EXE', os.join_path(cli.exe_dir, cli.exe_name), true) | ||
$if windows { | ||
exit(os.system('${os.quoted_path(tool_exe)} $tool_args')) | ||
} $else $if js { | ||
// no way to implement os.execvp in JS backend | ||
exit(os.system('$tool_exe $tool_args')) | ||
} $else { | ||
os.execvp(tool_exe, args) or { panic(err) } | ||
} | ||
exit(2) | ||
} | ||
exec := (tool_exe + ' ' + tool_args.join(' ')).trim_right(' ') | ||
v_message := if !os.is_executable(v) { ' (v was not found)' } else { '' } | ||
return error('${@MOD}.${@FN} failed executing "$exec"$v_message') | ||
} | ||
|
||
// string_to_args converts `input` string to an `os.args`-like array. | ||
// string_to_args preserves strings delimited by both `"` and `'`. | ||
pub fn string_to_args(input string) ![]string { | ||
mut args := []string{} | ||
mut buf := '' | ||
mut in_string := false | ||
mut delim := byte(` `) | ||
for ch in input { | ||
if ch in [`"`, `'`] { | ||
if !in_string { | ||
delim = ch | ||
} | ||
in_string = !in_string && ch == delim | ||
if !in_string { | ||
if buf != '' && buf != ' ' { | ||
args << buf | ||
} | ||
buf = '' | ||
delim = ` ` | ||
} | ||
continue | ||
} | ||
buf += ch.ascii_str() | ||
if !in_string && ch == ` ` { | ||
if buf != '' && buf != ' ' { | ||
args << buf[..buf.len - 1] | ||
} | ||
buf = '' | ||
continue | ||
} | ||
} | ||
if buf != '' && buf != ' ' { | ||
args << buf | ||
} | ||
if in_string { | ||
return error(@FN + | ||
': could not parse input, missing closing string delimiter `$delim.ascii_str()`') | ||
} | ||
return args | ||
} | ||
|
||
// validate_input validates `input` for use with shy. | ||
pub fn validate_input(input string) ! { | ||
input_ext := os.file_ext(input) | ||
|
||
accepted_input_ext := input_ext in cli.accepted_input_files | ||
if !(os.is_dir(input) || accepted_input_ext) { | ||
return error('input should be a V file or a directory containing V sources') | ||
} | ||
if accepted_input_ext { | ||
if !os.is_file(input) { | ||
return error('input should be a V file or a directory containing V sources') | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
module cli | ||
|
||
import os | ||
import shy.vxt | ||
|
||
// doctor prints various useful information to the shell to aid | ||
// diagnosticing the work environment. | ||
pub fn doctor(opt Options) { | ||
env_vars := os.environ() | ||
|
||
// shy section | ||
println('$exe_short_name | ||
Version $exe_version $exe_git_hash | ||
Path "$exe_dir"') | ||
|
||
// Shell environment | ||
print_var_if_set := fn (vars map[string]string, var_name string) { | ||
if var_name in vars { | ||
println('\t$var_name=' + os.getenv(var_name)) | ||
} | ||
} | ||
println('env') | ||
for env_var in shy_env_vars { | ||
print_var_if_set(env_vars, env_var) | ||
} | ||
|
||
// Product section | ||
println('Product | ||
Output "$opt.output"') | ||
|
||
// V section | ||
println('V | ||
Version $vxt.version() $vxt.version_commit_hash() | ||
Path "$vxt.home()"') | ||
if opt.v_flags.len > 0 { | ||
println('\tFlags $opt.v_flags') | ||
} | ||
// Print output of `v doctor` if v is found | ||
if vxt.found() { | ||
println('') | ||
v_cmd := [ | ||
vxt.vexe(), | ||
'doctor', | ||
] | ||
v_res := os.execute(v_cmd.join(' ')) | ||
out_lines := v_res.output.split('\n') | ||
for line in out_lines { | ||
println('\t$line') | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
module cli | ||
|
||
import os | ||
import flag | ||
|
||
pub struct Options { | ||
pub: | ||
// These fields would make little sense to change during a run | ||
verbosity int | ||
work_dir string = work_directory | ||
// | ||
run bool | ||
parallel bool = true // Run, what can be run, in parallel | ||
cache bool // defaults to false in os.args/flag parsing phase | ||
gles_version int // = android.default_gles_version | ||
// Detected environment | ||
dump_usage bool | ||
pub mut: | ||
// I/O | ||
input string | ||
output string | ||
additional_args []string // additional_args passed via os.args | ||
is_prod bool | ||
c_flags []string // flags passed to the C compiler(s) | ||
v_flags []string // flags passed to the V compiler | ||
assets_extra []string | ||
libs_extra []string | ||
} | ||
|
||
// options_from_env returns an `Option` struct filled with flags set via | ||
// the `SHY_FLAGS` env variable otherwise it returns a default `Option` struct. | ||
pub fn options_from_env(defaults Options) !Options { | ||
env_flags := os.getenv('SHY_FLAGS') | ||
if env_flags != '' { | ||
mut flags := [os.args[0]] | ||
flags << string_to_args(env_flags)! | ||
opts, _ := args_to_options(flags, defaults)! | ||
return opts | ||
} | ||
return defaults | ||
} | ||
|
||
// extend_from_dot_shy will merge the `Options` with any content | ||
// found in any `.shy` config files. | ||
pub fn (mut opt Options) extend_from_dot_shy() { | ||
// Look up values in input .shy file next to input if no flags or defaults was set | ||
// TODO use TOML format here | ||
// dot_shy_file := dot_shy_path(opt.input) | ||
// dot_shy := os.read_file(dot_shy_file) or { '' } | ||
} | ||
|
||
// validate_env ensures that `Options` meet all runtime requirements. | ||
pub fn (opt &Options) validate_env() ! { | ||
} | ||
|
||
// args_to_options returns an `Option` merged from (CLI/Shell) `arguments` using `defaults` as | ||
// values where no value can be obtained from `arguments`. | ||
pub fn args_to_options(arguments []string, defaults Options) !(Options, &flag.FlagParser) { | ||
mut args := arguments.clone() | ||
|
||
// Indentify sub-commands. | ||
for subcmd in subcmds { | ||
if subcmd in args { | ||
// First encountered known sub-command is executed on the spot. | ||
launch_cmd(args[args.index(subcmd)..]) or { | ||
eprintln(err) | ||
exit(1) | ||
} | ||
exit(0) | ||
} | ||
} | ||
|
||
mut v_flags := []string{} | ||
mut cmd_flags := []string{} | ||
// Indentify special flags in args before FlagParser ruin them. | ||
// E.g. the -autofree flag will result in dump_usage being called for some weird reason??? | ||
for special_flag in rip_vflags { | ||
if special_flag in args { | ||
if special_flag == '-gc' { | ||
gc_type := args[(args.index(special_flag)) + 1] | ||
v_flags << special_flag + ' $gc_type' | ||
args.delete(args.index(special_flag) + 1) | ||
} else if special_flag.starts_with('-') { | ||
v_flags << special_flag | ||
} else { | ||
cmd_flags << special_flag | ||
} | ||
args.delete(args.index(special_flag)) | ||
} | ||
} | ||
|
||
mut fp := flag.new_flag_parser(args) | ||
fp.application(exe_short_name) | ||
fp.version(version_full()) | ||
fp.description(exe_description) | ||
fp.arguments_description(exe_args_description) | ||
|
||
fp.skip_executable() | ||
|
||
mut verbosity := fp.int_opt('verbosity', `v`, 'Verbosity level 1-3') or { defaults.verbosity } | ||
// TODO implement FlagParser 'is_sat(name string) bool' or something in vlib for this usecase? | ||
if ('-v' in args || 'verbosity' in args) && verbosity == 0 { | ||
verbosity = 1 | ||
} | ||
|
||
mut opt := Options{ | ||
assets_extra: fp.string_multi('assets', `a`, 'Asset dir(s) to include in build') | ||
libs_extra: fp.string_multi('libs', `a`, 'Lib dir(s) to include in build') | ||
v_flags: fp.string_multi('flag', `f`, 'Additional flags for the V compiler') | ||
c_flags: fp.string_multi('cflag', `c`, 'Additional flags for the C compiler') | ||
gles_version: fp.int('gles', 0, defaults.gles_version, 'GLES version to use from any of 2,3') | ||
// | ||
run: 'run' in cmd_flags | ||
dump_usage: fp.bool('help', `h`, defaults.dump_usage, 'Show this help message and exit') | ||
cache: !fp.bool('nocache', 0, defaults.cache, 'Do not use build cache') | ||
// | ||
output: fp.string('output', `o`, defaults.output, 'Path to output (dir/file)') | ||
// | ||
verbosity: verbosity | ||
parallel: !fp.bool('no-parallel', 0, false, 'Do not run tasks in parallel.') | ||
// | ||
work_dir: defaults.work_dir | ||
} | ||
|
||
opt.additional_args = fp.finalize() or { | ||
return error(@FN + ': flag parser failed finalizing: $err') | ||
} | ||
|
||
mut c_flags := []string{} | ||
c_flags << opt.c_flags | ||
for c_flag in defaults.c_flags { | ||
if c_flag !in c_flags { | ||
c_flags << c_flag | ||
} | ||
} | ||
opt.c_flags = c_flags | ||
|
||
v_flags << opt.v_flags | ||
for v_flag in defaults.v_flags { | ||
if v_flag !in v_flags { | ||
v_flags << v_flag | ||
} | ||
} | ||
opt.v_flags = v_flags | ||
|
||
return opt, fp | ||
} |
Oops, something went wrong.