From f5c4671bfbad96bf346bd7e9a21fc4317b4959df Mon Sep 17 00:00:00 2001
From: Indrajith K L
Date: Sat, 3 Dec 2022 17:00:20 +0530
Subject: Adds most of the tools

---
 v_windows/v/vlib/cli/README.md      |  30 ++++
 v_windows/v/vlib/cli/command.v      | 307 ++++++++++++++++++++++++++++++++++++
 v_windows/v/vlib/cli/command_test.v | 222 ++++++++++++++++++++++++++
 v_windows/v/vlib/cli/flag.v         | 296 ++++++++++++++++++++++++++++++++++
 v_windows/v/vlib/cli/flag_test.v    | 216 +++++++++++++++++++++++++
 v_windows/v/vlib/cli/help.v         | 176 +++++++++++++++++++++
 v_windows/v/vlib/cli/help_test.v    |  65 ++++++++
 v_windows/v/vlib/cli/version.v      |  25 +++
 8 files changed, 1337 insertions(+)
 create mode 100644 v_windows/v/vlib/cli/README.md
 create mode 100644 v_windows/v/vlib/cli/command.v
 create mode 100644 v_windows/v/vlib/cli/command_test.v
 create mode 100644 v_windows/v/vlib/cli/flag.v
 create mode 100644 v_windows/v/vlib/cli/flag_test.v
 create mode 100644 v_windows/v/vlib/cli/help.v
 create mode 100644 v_windows/v/vlib/cli/help_test.v
 create mode 100644 v_windows/v/vlib/cli/version.v

(limited to 'v_windows/v/vlib/cli')

diff --git a/v_windows/v/vlib/cli/README.md b/v_windows/v/vlib/cli/README.md
new file mode 100644
index 0000000..93d8d62
--- /dev/null
+++ b/v_windows/v/vlib/cli/README.md
@@ -0,0 +1,30 @@
+Usage example:
+
+```v
+module main
+
+import os
+import cli
+
+fn main() {
+	mut app := cli.Command{
+		name: 'example-app'
+		description: 'example-app'
+		execute: fn (cmd cli.Command) ? {
+			println('hello app')
+			return
+		}
+		commands: [
+			cli.Command{
+				name: 'sub'
+				execute: fn (cmd cli.Command) ? {
+					println('hello subcommand')
+					return
+				}
+			},
+		]
+	}
+	app.setup()
+	app.parse(os.args)
+}
+```
diff --git a/v_windows/v/vlib/cli/command.v b/v_windows/v/vlib/cli/command.v
new file mode 100644
index 0000000..d242e00
--- /dev/null
+++ b/v_windows/v/vlib/cli/command.v
@@ -0,0 +1,307 @@
+module cli
+
+type FnCommandCallback = fn (cmd Command) ?
+
+// str returns the `string` representation of the callback.
+pub fn (f FnCommandCallback) str() string {
+	return 'FnCommandCallback=>' + ptr_str(f)
+}
+
+// Command is a structured representation of a single command
+// or chain of commands.
+pub struct Command {
+pub mut:
+	name            string
+	usage           string
+	description     string
+	version         string
+	pre_execute     FnCommandCallback
+	execute         FnCommandCallback
+	post_execute    FnCommandCallback
+	disable_help    bool
+	disable_version bool
+	disable_flags   bool
+	sort_flags      bool
+	sort_commands   bool
+	parent          &Command = 0
+	commands        []Command
+	flags           []Flag
+	required_args   int
+	args            []string
+	posix_mode      bool
+}
+
+// str returns the `string` representation of the `Command`.
+pub fn (cmd Command) str() string {
+	mut res := []string{}
+	res << 'Command{'
+	res << '	name: "$cmd.name"'
+	res << '	usage: "$cmd.usage"'
+	res << '	version: "$cmd.version"'
+	res << '	description: "$cmd.description"'
+	res << '	disable_help: $cmd.disable_help'
+	res << '	disable_flags: $cmd.disable_flags'
+	res << '	disable_version: $cmd.disable_version'
+	res << '	sort_flags: $cmd.sort_flags'
+	res << '	sort_commands: $cmd.sort_commands'
+	res << '	cb execute: $cmd.execute'
+	res << '	cb pre_execute: $cmd.pre_execute'
+	res << '	cb post_execute: $cmd.post_execute'
+	if cmd.parent == 0 {
+		res << '	parent: &Command(0)'
+	} else {
+		res << '	parent: &Command{$cmd.parent.name ...}'
+	}
+	res << '	commands: $cmd.commands'
+	res << '	flags: $cmd.flags'
+	res << '	required_args: $cmd.required_args'
+	res << '	args: $cmd.args'
+	res << '}'
+	return res.join('\n')
+}
+
+// is_root returns `true` if this `Command` has no parents.
+pub fn (cmd Command) is_root() bool {
+	return isnil(cmd.parent)
+}
+
+// root returns the root `Command` of the command chain.
+pub fn (cmd Command) root() Command {
+	if cmd.is_root() {
+		return cmd
+	}
+	return cmd.parent.root()
+}
+
+// full_name returns the full `string` representation of all commands int the chain.
+pub fn (cmd Command) full_name() string {
+	if cmd.is_root() {
+		return cmd.name
+	}
+	return cmd.parent.full_name() + ' $cmd.name'
+}
+
+// add_commands adds the `commands` array of `Command`s as sub-commands.
+pub fn (mut cmd Command) add_commands(commands []Command) {
+	for command in commands {
+		cmd.add_command(command)
+	}
+}
+
+// add_command adds `command` as a sub-command of this `Command`.
+pub fn (mut cmd Command) add_command(command Command) {
+	mut subcmd := command
+	if cmd.commands.contains(subcmd.name) {
+		println('Command with the name `$subcmd.name` already exists')
+		exit(1)
+	}
+	subcmd.parent = unsafe { cmd }
+	cmd.commands << subcmd
+}
+
+// setup ensures that all sub-commands of this `Command`
+// is linked as a chain.
+pub fn (mut cmd Command) setup() {
+	for mut subcmd in cmd.commands {
+		subcmd.parent = unsafe { cmd }
+		subcmd.posix_mode = cmd.posix_mode
+		subcmd.setup()
+	}
+}
+
+// add_flags adds the array `flags` to this `Command`.
+pub fn (mut cmd Command) add_flags(flags []Flag) {
+	for flag in flags {
+		cmd.add_flag(flag)
+	}
+}
+
+// add_flag adds `flag` to this `Command`.
+pub fn (mut cmd Command) add_flag(flag Flag) {
+	if cmd.flags.contains(flag.name) {
+		println('Flag with the name `$flag.name` already exists')
+		exit(1)
+	}
+	cmd.flags << flag
+}
+
+// parse parses `args` into this structured `Command`.
+pub fn (mut cmd Command) parse(args []string) {
+	if !cmd.disable_flags {
+		cmd.add_default_flags()
+	}
+	cmd.add_default_commands()
+	if cmd.sort_flags {
+		cmd.flags.sort(a.name < b.name)
+	}
+	if cmd.sort_commands {
+		cmd.commands.sort(a.name < b.name)
+	}
+	cmd.args = args[1..]
+	if !cmd.disable_flags {
+		cmd.parse_flags()
+	}
+	cmd.parse_commands()
+}
+
+// add_default_flags adds the commonly used `-h`/`--help` and
+// `-v`/`--version` flags to the `Command`.
+fn (mut cmd Command) add_default_flags() {
+	if !cmd.disable_help && !cmd.flags.contains('help') {
+		use_help_abbrev := !cmd.flags.contains('h') && cmd.posix_mode
+		cmd.add_flag(help_flag(use_help_abbrev))
+	}
+	if !cmd.disable_version && cmd.version != '' && !cmd.flags.contains('version') {
+		use_version_abbrev := !cmd.flags.contains('v') && cmd.posix_mode
+		cmd.add_flag(version_flag(use_version_abbrev))
+	}
+}
+
+// add_default_commands adds the command functions of the
+// commonly used `help` and `version` flags to the `Command`.
+fn (mut cmd Command) add_default_commands() {
+	if !cmd.disable_help && !cmd.commands.contains('help') && cmd.is_root() {
+		cmd.add_command(help_cmd())
+	}
+	if !cmd.disable_version && cmd.version != '' && !cmd.commands.contains('version') {
+		cmd.add_command(version_cmd())
+	}
+}
+
+fn (mut cmd Command) parse_flags() {
+	for {
+		if cmd.args.len < 1 || !cmd.args[0].starts_with('-') {
+			break
+		}
+		mut found := false
+		for i in 0 .. cmd.flags.len {
+			unsafe {
+				mut flag := &cmd.flags[i]
+				if flag.matches(cmd.args, cmd.posix_mode) {
+					found = true
+					flag.found = true
+					cmd.args = flag.parse(cmd.args, cmd.posix_mode) or {
+						println('Failed to parse flag `${cmd.args[0]}`: $err')
+						exit(1)
+					}
+					break
+				}
+			}
+		}
+		if !found {
+			println('Command `$cmd.name` has no flag `${cmd.args[0]}`')
+			exit(1)
+		}
+	}
+}
+
+fn (mut cmd Command) parse_commands() {
+	global_flags := cmd.flags.filter(it.global)
+	cmd.check_help_flag()
+	cmd.check_version_flag()
+	for i in 0 .. cmd.args.len {
+		arg := cmd.args[i]
+		for j in 0 .. cmd.commands.len {
+			mut command := cmd.commands[j]
+			if command.name == arg {
+				for flag in global_flags {
+					command.add_flag(flag)
+				}
+				command.parse(cmd.args[i..])
+				return
+			}
+		}
+	}
+	if cmd.is_root() && isnil(cmd.execute) {
+		if !cmd.disable_help {
+			cmd.execute_help()
+			return
+		}
+	}
+	// if no further command was found, execute current command
+	if cmd.required_args > 0 {
+		if cmd.required_args > cmd.args.len {
+			eprintln('Command `$cmd.name` needs at least $cmd.required_args arguments')
+			exit(1)
+		}
+	}
+	cmd.check_required_flags()
+	if !isnil(cmd.pre_execute) {
+		cmd.pre_execute(*cmd) or {
+			eprintln('cli preexecution error: $err')
+			exit(1)
+		}
+	}
+	if !isnil(cmd.execute) {
+		cmd.execute(*cmd) or {
+			eprintln('cli execution error: $err')
+			exit(1)
+		}
+	}
+	if !isnil(cmd.post_execute) {
+		cmd.post_execute(*cmd) or {
+			eprintln('cli postexecution error: $err')
+			exit(1)
+		}
+	}
+}
+
+fn (cmd Command) check_help_flag() {
+	if !cmd.disable_help && cmd.flags.contains('help') {
+		help_flag := cmd.flags.get_bool('help') or { return } // ignore error and handle command normally
+		if help_flag {
+			cmd.execute_help()
+			exit(0)
+		}
+	}
+}
+
+fn (cmd Command) check_version_flag() {
+	if !cmd.disable_version && cmd.version != '' && cmd.flags.contains('version') {
+		version_flag := cmd.flags.get_bool('version') or { return } // ignore error and handle command normally
+		if version_flag {
+			version_cmd := cmd.commands.get('version') or { return } // ignore error and handle command normally
+			version_cmd.execute(version_cmd) or { panic(err) }
+			exit(0)
+		}
+	}
+}
+
+fn (cmd Command) check_required_flags() {
+	for flag in cmd.flags {
+		if flag.required && flag.value.len == 0 {
+			full_name := cmd.full_name()
+			println('Flag `$flag.name` is required by `$full_name`')
+			exit(1)
+		}
+	}
+}
+
+// execute_help executes the callback registered
+// for the `-h`/`--help` flag option.
+pub fn (cmd Command) execute_help() {
+	if cmd.commands.contains('help') {
+		help_cmd := cmd.commands.get('help') or { return } // ignore error and handle command normally
+		help_cmd.execute(help_cmd) or { panic(err) }
+	} else {
+		print(cmd.help_message())
+	}
+}
+
+fn (cmds []Command) get(name string) ?Command {
+	for cmd in cmds {
+		if cmd.name == name {
+			return cmd
+		}
+	}
+	return error('Command `$name` not found in $cmds')
+}
+
+fn (cmds []Command) contains(name string) bool {
+	for cmd in cmds {
+		if cmd.name == name {
+			return true
+		}
+	}
+	return false
+}
diff --git a/v_windows/v/vlib/cli/command_test.v b/v_windows/v/vlib/cli/command_test.v
new file mode 100644
index 0000000..e3c69b1
--- /dev/null
+++ b/v_windows/v/vlib/cli/command_test.v
@@ -0,0 +1,222 @@
+import cli
+
+fn test_if_command_parses_empty_args() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: empty_func
+	}
+	cmd.parse(['command'])
+	assert cmd.name == 'command' && compare_arrays(cmd.args, [])
+}
+
+fn test_if_command_parses_args() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: empty_func
+	}
+	cmd.parse(['command', 'arg0', 'arg1'])
+	assert cmd.name == 'command' && compare_arrays(cmd.args, ['arg0', 'arg1'])
+}
+
+fn test_if_subcommands_parse_args() {
+	mut cmd := cli.Command{
+		name: 'command'
+	}
+	subcmd := cli.Command{
+		name: 'subcommand'
+		execute: if_subcommands_parse_args_func
+	}
+	cmd.add_command(subcmd)
+	cmd.parse(['command', 'subcommand', 'arg0', 'arg1'])
+}
+
+fn if_subcommands_parse_args_func(cmd cli.Command) ? {
+	assert cmd.name == 'subcommand' && compare_arrays(cmd.args, ['arg0', 'arg1'])
+}
+
+fn test_if_command_has_default_help_subcommand() {
+	mut cmd := cli.Command{
+		name: 'command'
+	}
+	cmd.parse(['command'])
+	assert has_command(cmd, 'help')
+}
+
+fn test_if_command_has_default_version_subcommand_if_version_is_set() {
+	mut cmd := cli.Command{
+		name: 'command'
+		version: '1.0.0'
+	}
+	cmd.parse(['command'])
+	assert has_command(cmd, 'version')
+}
+
+fn flag_should_be_set(cmd cli.Command) ? {
+	flag := cmd.flags.get_string('flag') ?
+	assert flag == 'value'
+}
+
+fn test_if_flag_gets_set() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: flag_should_be_set
+	}
+	cmd.add_flag(cli.Flag{
+		flag: .string
+		name: 'flag'
+	})
+	cmd.parse(['command', '-flag', 'value'])
+}
+
+fn test_if_flag_gets_set_with_abbrev() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: flag_should_be_set
+	}
+	cmd.add_flag(cli.Flag{
+		flag: .string
+		name: 'flag'
+		abbrev: 'f'
+	})
+	cmd.parse(['command', '-f', 'value'])
+}
+
+fn test_if_flag_gets_set_with_long_arg() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: flag_should_be_set
+		posix_mode: true
+	}
+	cmd.add_flag(cli.Flag{
+		flag: .string
+		name: 'flag'
+		abbrev: 'f'
+	})
+	cmd.parse(['command', '--flag', 'value'])
+}
+
+fn flag_should_have_value_of_42(cmd cli.Command) ? {
+	flag := cmd.flags.get_string('flag') ?
+	assert flag == 'value'
+	value := cmd.flags.get_int('value') ?
+	assert value == 42
+}
+
+fn test_if_multiple_flags_get_set() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: flag_should_have_value_of_42
+	}
+	cmd.add_flag(cli.Flag{
+		flag: .string
+		name: 'flag'
+	})
+	cmd.add_flag(cli.Flag{
+		flag: .int
+		name: 'value'
+	})
+	cmd.parse(['command', '-flag', 'value', '-value', '42'])
+}
+
+fn test_if_required_flags_get_set() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: flag_should_have_value_of_42
+	}
+	cmd.add_flag(cli.Flag{
+		flag: .string
+		name: 'flag'
+	})
+	cmd.add_flag(cli.Flag{
+		flag: .int
+		name: 'value'
+		required: true
+	})
+	cmd.parse(['command', '-flag', 'value', '-value', '42'])
+}
+
+fn flag_is_set_in_subcommand(cmd cli.Command) ? {
+	flag := cmd.flags.get_string('flag') or { panic(err) }
+	assert flag == 'value'
+}
+
+fn test_if_flag_gets_set_in_subcommand() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: empty_func
+	}
+	mut subcmd := cli.Command{
+		name: 'subcommand'
+		execute: flag_is_set_in_subcommand
+	}
+	subcmd.add_flag(cli.Flag{
+		flag: .string
+		name: 'flag'
+	})
+	cmd.add_command(subcmd)
+	cmd.parse(['command', 'subcommand', '-flag', 'value'])
+}
+
+fn test_if_global_flag_gets_set_in_subcommand() {
+	mut cmd := cli.Command{
+		name: 'command'
+		execute: empty_func
+	}
+	cmd.add_flag(cli.Flag{
+		flag: .string
+		name: 'flag'
+		global: true
+	})
+	subcmd := cli.Command{
+		name: 'subcommand'
+		execute: flag_is_set_in_subcommand
+	}
+	cmd.add_command(subcmd)
+	cmd.parse(['command', '-flag', 'value', 'subcommand'])
+}
+
+fn test_command_setup() {
+	mut cmd := cli.Command{
+		name: 'root'
+		commands: [
+			cli.Command{
+				name: 'child'
+				commands: [
+					cli.Command{
+						name: 'child-child'
+					},
+				]
+			},
+		]
+	}
+	assert isnil(cmd.commands[0].parent)
+	assert isnil(cmd.commands[0].commands[0].parent)
+	cmd.setup()
+	assert cmd.commands[0].parent.name == 'root'
+	assert cmd.commands[0].commands[0].parent.name == 'child'
+}
+
+// helper functions
+fn empty_func(cmd cli.Command) ? {
+}
+
+fn has_command(cmd cli.Command, name string) bool {
+	for subcmd in cmd.commands {
+		if subcmd.name == name {
+			return true
+		}
+	}
+	return false
+}
+
+fn compare_arrays(array0 []string, array1 []string) bool {
+	if array0.len != array1.len {
+		return false
+	}
+	for i in 0 .. array0.len {
+		if array0[i] != array1[i] {
+			return false
+		}
+	}
+	return true
+}
diff --git a/v_windows/v/vlib/cli/flag.v b/v_windows/v/vlib/cli/flag.v
new file mode 100644
index 0000000..fb8fa09
--- /dev/null
+++ b/v_windows/v/vlib/cli/flag.v
@@ -0,0 +1,296 @@
+module cli
+
+pub enum FlagType {
+	bool
+	int
+	float
+	string
+	// If flag can set multiple time, use array type
+	int_array
+	float_array
+	string_array
+}
+
+// Flag holds information for a command line flag.
+// (flags are also commonly referred to as "options" or "switches")
+// These are typically denoted in the shell by a short form `-f` and/or a long form `--flag`
+pub struct Flag {
+pub mut:
+	flag FlagType
+	// Name of flag
+	name string
+	// Like short option
+	abbrev string
+	// Desciption of flag
+	description string
+	global      bool
+	// If flag is requierd
+	required bool
+	// Default value if no value provide by command line
+	default_value []string = []
+mut:
+	// Set true if flag found.
+	found bool
+	// Value of flag
+	value []string = []
+}
+
+// get_all_found returns an array of all `Flag`s found in the command parameters
+pub fn (flags []Flag) get_all_found() []Flag {
+	return flags.filter(it.found)
+}
+
+// get_bool returns `true` if the flag is set.
+// get_bool returns an error if the `FlagType` is not boolean.
+pub fn (flag Flag) get_bool() ?bool {
+	if flag.flag != .bool {
+		return error('$flag.name: Invalid flag type `$flag.flag`, expected `bool`')
+	}
+
+	val := flag.get_value_or_default_value()
+
+	return val.len > 0 && val[0] == 'true'
+}
+
+// get_bool returns `true` if the flag specified in `name` is set.
+// get_bool returns an error if the `FlagType` is not boolean.
+pub fn (flags []Flag) get_bool(name string) ?bool {
+	flag := flags.get(name) ?
+	return flag.get_bool()
+}
+
+// get_int returns the `int` value argument of the flag.
+// get_int returns an error if the `FlagType` is not integer.
+pub fn (flag Flag) get_int() ?int {
+	if flag.flag != .int {
+		return error('$flag.name: Invalid flag type `$flag.flag`, expected `int`')
+	}
+
+	val := flag.get_value_or_default_value()
+
+	if val.len == 0 {
+		return 0
+	} else {
+		return val[0].int()
+	}
+}
+
+// get_ints returns the array of `int` value argument of the flag specified in `name`.
+// get_ints returns an error if the `FlagType` is not integer.
+pub fn (flag Flag) get_ints() ?[]int {
+	if flag.flag != .int_array {
+		return error('$flag.name: Invalid flag type `$flag.flag`, expected `int_array`')
+	}
+
+	val := flag.get_value_or_default_value()
+
+	if val.len == 0 {
+		return []int{}
+	} else {
+		mut values := []int{}
+
+		for f in val {
+			values << f.int()
+		}
+
+		return values
+	}
+}
+
+// get_int returns the `int` value argument of the flag specified in `name`.
+// get_int returns an error if the `FlagType` is not integer.
+pub fn (flags []Flag) get_int(name string) ?int {
+	flag := flags.get(name) ?
+	return flag.get_int()
+}
+
+// get_ints returns the array of `int` value argument of the flag specified in `name`.
+// get_ints returns an error if the `FlagType` is not integer.
+pub fn (flags []Flag) get_ints(name string) ?[]int {
+	flag := flags.get(name) ?
+	return flag.get_ints()
+}
+
+// get_float returns the `f64` value argument of the flag.
+// get_float returns an error if the `FlagType` is not floating point.
+pub fn (flag Flag) get_float() ?f64 {
+	if flag.flag != .float {
+		return error('$flag.name: Invalid flag type `$flag.flag`, expected `float`')
+	}
+
+	val := flag.get_value_or_default_value()
+
+	if val.len == 0 {
+		return 0.0
+	} else {
+		return val[0].f64()
+	}
+}
+
+// get_floats returns the `f64` value argument of the flag.
+// get_floats returns an error if the `FlagType` is not floating point.
+pub fn (flag Flag) get_floats() ?[]f64 {
+	if flag.flag != .float_array {
+		return error('$flag.name: Invalid flag type `$flag.flag`, expected `float_array`')
+	}
+
+	val := flag.get_value_or_default_value()
+
+	if val.len == 0 {
+		return []f64{}
+	} else {
+		mut values := []f64{}
+
+		for f in val {
+			values << f.f64()
+		}
+
+		return values
+	}
+}
+
+// get_float returns the `f64` value argument of the flag specified in `name`.
+// get_float returns an error if the `FlagType` is not floating point.
+pub fn (flags []Flag) get_float(name string) ?f64 {
+	flag := flags.get(name) ?
+	return flag.get_float()
+}
+
+// get_floats returns the array of `f64` value argument of the flag specified in `name`.
+// get_floats returns an error if the `FlagType` is not floating point.
+pub fn (flags []Flag) get_floats(name string) ?[]f64 {
+	flag := flags.get(name) ?
+	return flag.get_floats()
+}
+
+// get_string returns the `string` value argument of the flag.
+// get_string returns an error if the `FlagType` is not string.
+pub fn (flag Flag) get_string() ?string {
+	if flag.flag != .string {
+		return error('$flag.name: Invalid flag type `$flag.flag`, expected `string`')
+	}
+
+	val := flag.get_value_or_default_value()
+
+	if val.len == 0 {
+		return ''
+	} else {
+		return val[0]
+	}
+}
+
+// get_strings returns the array of `string` value argument of the flag.
+// get_strings returns an error if the `FlagType` is not string.
+pub fn (flag Flag) get_strings() ?[]string {
+	if flag.flag != .string_array {
+		return error('$flag.name: Invalid flag type `$flag.flag`, expected `string_array`')
+	}
+
+	val := flag.get_value_or_default_value()
+
+	if val.len == 0 {
+		return []string{}
+	} else {
+		return val
+	}
+}
+
+// get_string returns the `string` value argument of the flag specified in `name`.
+// get_string returns an error if the `FlagType` is not string.
+pub fn (flags []Flag) get_string(name string) ?string {
+	flag := flags.get(name) ?
+	return flag.get_string()
+}
+
+// get_strings returns the `string` value argument of the flag specified in `name`.
+// get_strings returns an error if the `FlagType` is not string.
+pub fn (flags []Flag) get_strings(name string) ?[]string {
+	flag := flags.get(name) ?
+	return flag.get_strings()
+}
+
+// parse parses flag values from arguments and return
+// an array of arguments with all consumed elements removed.
+fn (mut flag Flag) parse(args []string, posix_mode bool) ?[]string {
+	if flag.matches(args, posix_mode) {
+		if flag.flag == .bool {
+			new_args := flag.parse_bool(args) ?
+			return new_args
+		} else {
+			if flag.value.len > 0 && flag.flag != .int_array && flag.flag != .float_array
+				&& flag.flag != .string_array {
+				return error('The argument `$flag.name` accept only one value!')
+			}
+
+			new_args := flag.parse_raw(args) ?
+			return new_args
+		}
+	} else {
+		return args
+	}
+}
+
+// matches returns `true` if first arg in `args` matches this flag.
+fn (mut flag Flag) matches(args []string, posix_mode bool) bool {
+	prefix := if posix_mode { '--' } else { '-' }
+	return (flag.name != '' && args[0] == '$prefix$flag.name')
+		|| (flag.name != '' && args[0].starts_with('$prefix$flag.name='))
+		|| (flag.abbrev != '' && args[0] == '-$flag.abbrev')
+		|| (flag.abbrev != '' && args[0].starts_with('-$flag.abbrev='))
+}
+
+fn (mut flag Flag) parse_raw(args []string) ?[]string {
+	if args[0].len > flag.name.len && args[0].contains('=') {
+		flag.value << args[0].split('=')[1]
+		return args[1..]
+	} else if args.len >= 2 {
+		flag.value << args[1]
+		return args[2..]
+	}
+	return error('Missing argument for `$flag.name`')
+}
+
+fn (mut flag Flag) parse_bool(args []string) ?[]string {
+	if args[0].len > flag.name.len && args[0].contains('=') {
+		flag.value = [args[0].split('=')[1]]
+		return args[1..]
+	} else if args.len >= 2 {
+		if args[1] in ['true', 'false'] {
+			flag.value = [args[1]]
+			return args[2..]
+		}
+	}
+	// In fact bool cannot be multiple
+	flag.value = ['true']
+	return args[1..]
+}
+
+// get returns the `Flag` matching `name` or an error
+// if it can't be found.
+fn (flags []Flag) get(name string) ?Flag {
+	for flag in flags {
+		if flag.name == name {
+			return flag
+		}
+	}
+	return error('Flag `$name` not found in $flags')
+}
+
+fn (flags []Flag) contains(name string) bool {
+	for flag in flags {
+		if flag.name == name || flag.abbrev == name {
+			return true
+		}
+	}
+	return false
+}
+
+// Check if value is set by command line option. If not, return default value.
+fn (flag Flag) get_value_or_default_value() []string {
+	if flag.value.len == 0 && flag.default_value.len > 0 {
+		// If default value is set and no value provide, use default value.
+		return flag.default_value
+	} else {
+		return flag.value
+	}
+}
diff --git a/v_windows/v/vlib/cli/flag_test.v b/v_windows/v/vlib/cli/flag_test.v
new file mode 100644
index 0000000..a40a7c0
--- /dev/null
+++ b/v_windows/v/vlib/cli/flag_test.v
@@ -0,0 +1,216 @@
+import cli
+
+fn test_if_string_flag_parses() {
+	mut flag := cli.Flag{
+		flag: .string
+		name: 'flag'
+	}
+	flag.parse(['-flag', 'value1'], false) or { panic(err) }
+	mut value := flag.get_string() or { panic(err) }
+	assert value == 'value1'
+
+	flag = cli.Flag{
+		flag: .string
+		name: 'flag'
+	}
+	flag.parse(['-flag=value2'], false) or { panic(err) }
+	value = flag.get_string() or { panic(err) }
+	assert value == 'value2'
+
+	flag = cli.Flag{
+		flag: .string_array
+		name: 'flag'
+	}
+	flag.parse(['-flag=value1'], false) or { panic(err) }
+	flag.parse(['-flag=value2'], false) or { panic(err) }
+	mut values := flag.get_strings() or { panic(err) }
+	assert values == ['value1', 'value2']
+
+	flags := [
+		cli.Flag{
+			flag: .string_array
+			name: 'flag'
+			value: ['a', 'b', 'c']
+		},
+		cli.Flag{
+			flag: .string
+			name: 'flag2'
+		},
+	]
+
+	values = flags.get_strings('flag') or { panic(err) }
+	assert values == ['a', 'b', 'c']
+}
+
+fn test_if_bool_flag_parses() {
+	mut flag := cli.Flag{
+		flag: .bool
+		name: 'flag'
+	}
+	mut value := false
+	flag.parse(['-flag'], false) or { panic(err) }
+	value = flag.get_bool() or { panic(err) }
+	assert value == true
+	flag.parse(['-flag', 'false'], false) or { panic(err) }
+	value = flag.get_bool() or { panic(err) }
+	assert value == false
+	flag.parse(['-flag', 'true'], false) or { panic(err) }
+	value = flag.get_bool() or { panic(err) }
+	assert value == true
+	flag.parse(['-flag=false'], false) or { panic(err) }
+	value = flag.get_bool() or { panic(err) }
+	assert value == false
+	flag.parse(['-flag=true'], false) or { panic(err) }
+	value = flag.get_bool() or { panic(err) }
+	assert value == true
+}
+
+fn test_if_int_flag_parses() {
+	mut flag := cli.Flag{
+		flag: .int
+		name: 'flag'
+	}
+
+	mut value := 0
+	flag.parse(['-flag', '42'], false) or { panic(err) }
+	value = flag.get_int() or { panic(err) }
+	assert value == 42
+
+	flag = cli.Flag{
+		flag: .int
+		name: 'flag'
+	}
+
+	flag.parse(['-flag=45'], false) or { panic(err) }
+	value = flag.get_int() or { panic(err) }
+	assert value == 45
+
+	flag = cli.Flag{
+		flag: .int_array
+		name: 'flag'
+	}
+
+	flag.parse(['-flag=42'], false) or { panic(err) }
+	flag.parse(['-flag=45'], false) or { panic(err) }
+	mut values := flag.get_ints() or { panic(err) }
+	assert values == [42, 45]
+
+	flags := [
+		cli.Flag{
+			flag: .int_array
+			name: 'flag'
+			value: ['1', '2', '3']
+		},
+		cli.Flag{
+			flag: .int
+			name: 'flag2'
+		},
+	]
+
+	values = flags.get_ints('flag') or { panic(err) }
+	assert values == [1, 2, 3]
+}
+
+fn test_if_float_flag_parses() {
+	mut flag := cli.Flag{
+		flag: .float
+		name: 'flag'
+	}
+	mut value := f64(0)
+	flag.parse(['-flag', '3.14158'], false) or { panic(err) }
+	value = flag.get_float() or { panic(err) }
+	assert value == 3.14158
+
+	flag = cli.Flag{
+		flag: .float
+		name: 'flag'
+	}
+
+	flag.parse(['-flag=3.14159'], false) or { panic(err) }
+	value = flag.get_float() or { panic(err) }
+	assert value == 3.14159
+
+	flag = cli.Flag{
+		flag: .float_array
+		name: 'flag'
+	}
+
+	flag.parse(['-flag=3.1'], false) or { panic(err) }
+	flag.parse(['-flag=1.3'], false) or { panic(err) }
+	mut values := flag.get_floats() or { panic(err) }
+	assert values == [3.1, 1.3]
+
+	flags := [
+		cli.Flag{
+			flag: .float_array
+			name: 'flag'
+			value: ['1.1', '2.2', '3.3']
+		},
+		cli.Flag{
+			flag: .float
+			name: 'flag2'
+		},
+	]
+
+	values = flags.get_floats('flag') or { panic(err) }
+	assert values == [1.1, 2.2, 3.3]
+}
+
+fn test_if_flag_parses_with_abbrev() {
+	mut flag := cli.Flag{
+		flag: .bool
+		name: 'flag'
+		abbrev: 'f'
+	}
+	mut value := false
+	flag.parse(['--flag'], true) or { panic(err) }
+	value = flag.get_bool() or { panic(err) }
+	assert value == true
+
+	value = false
+	flag = cli.Flag{
+		flag: .bool
+		name: 'flag'
+		abbrev: 'f'
+	}
+	flag.parse(['-f'], true) or { panic(err) }
+	value = flag.get_bool() or { panic(err) }
+	assert value == true
+}
+
+fn test_if_multiple_value_on_single_value() {
+	mut flag := cli.Flag{
+		flag: .float
+		name: 'flag'
+	}
+
+	flag.parse(['-flag', '3.14158'], false) or { panic(err) }
+
+	if _ := flag.parse(['-flag', '3.222'], false) {
+		panic("No multiple value flag don't raise an error!")
+	} else {
+		assert true
+	}
+}
+
+fn test_default_value() {
+	mut flag := cli.Flag{
+		flag: .float
+		name: 'flag'
+		default_value: ['1.234']
+	}
+
+	flag.parse(['-flag', '3.14158'], false) or { panic(err) }
+	mut value := flag.get_float() or { panic(err) }
+	assert value == 3.14158
+
+	flag = cli.Flag{
+		flag: .float
+		name: 'flag'
+		default_value: ['1.234']
+	}
+
+	flag.parse([''], false) or { panic(err) }
+	value = flag.get_float() or { panic(err) }
+	assert value == 1.234
+}
diff --git a/v_windows/v/vlib/cli/help.v b/v_windows/v/vlib/cli/help.v
new file mode 100644
index 0000000..f47f90e
--- /dev/null
+++ b/v_windows/v/vlib/cli/help.v
@@ -0,0 +1,176 @@
+module cli
+
+import term
+import strings
+
+const (
+	base_indent_len            = 2
+	min_description_indent_len = 20
+	spacing                    = 2
+)
+
+fn help_flag(with_abbrev bool) Flag {
+	sabbrev := if with_abbrev { 'h' } else { '' }
+	return Flag{
+		flag: .bool
+		name: 'help'
+		abbrev: sabbrev
+		description: 'Prints help information.'
+	}
+}
+
+fn help_cmd() Command {
+	return Command{
+		name: 'help'
+		usage: '<command>'
+		description: 'Prints help information.'
+		execute: print_help_for_command
+	}
+}
+
+fn print_help_for_command(help_cmd Command) ? {
+	if help_cmd.args.len > 0 {
+		mut cmd := help_cmd.parent
+		for arg in help_cmd.args {
+			mut found := false
+			for sub_cmd in cmd.commands {
+				if sub_cmd.name == arg {
+					cmd = unsafe { &sub_cmd }
+					found = true
+					break
+				}
+			}
+			if !found {
+				args := help_cmd.args.join(' ')
+				println('Invalid command: $args')
+				return
+			}
+		}
+		print(cmd.help_message())
+	} else {
+		if help_cmd.parent != 0 {
+			print(help_cmd.parent.help_message())
+		}
+	}
+}
+
+fn (cmd Command) help_message() string {
+	mut help := ''
+	help += 'Usage: $cmd.full_name()'
+	if cmd.flags.len > 0 {
+		help += ' [flags]'
+	}
+	if cmd.commands.len > 0 {
+		help += ' [commands]'
+	}
+	if cmd.usage.len > 0 {
+		help += ' $cmd.usage'
+	} else {
+		for i in 0 .. cmd.required_args {
+			help += ' <arg$i>'
+		}
+	}
+	help += '\n'
+	if cmd.description != '' {
+		help += '\n$cmd.description\n'
+	}
+	mut abbrev_len := 0
+	mut name_len := cli.min_description_indent_len
+	if cmd.posix_mode {
+		for flag in cmd.flags {
+			if flag.abbrev != '' {
+				abbrev_len = max(abbrev_len, flag.abbrev.len + cli.spacing + 1) // + 1 for '-' in front
+			}
+			name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 2) // + 2 for '--' in front
+		}
+		for command in cmd.commands {
+			name_len = max(name_len, command.name.len + cli.spacing)
+		}
+	} else {
+		for flag in cmd.flags {
+			if flag.abbrev != '' {
+				abbrev_len = max(abbrev_len, flag.abbrev.len + cli.spacing + 1) // + 1 for '-' in front
+			}
+			name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 1) // + 1 for '-' in front
+		}
+		for command in cmd.commands {
+			name_len = max(name_len, command.name.len + cli.spacing)
+		}
+	}
+	if cmd.flags.len > 0 {
+		help += '\nFlags:\n'
+		for flag in cmd.flags {
+			mut flag_name := ''
+			prefix := if cmd.posix_mode { '--' } else { '-' }
+			if flag.abbrev != '' {
+				abbrev_indent := ' '.repeat(abbrev_len - flag.abbrev.len - 1) // - 1 for '-' in front
+				flag_name = '-$flag.abbrev$abbrev_indent$prefix$flag.name'
+			} else {
+				abbrev_indent := ' '.repeat(abbrev_len)
+				flag_name = '$abbrev_indent$prefix$flag.name'
+			}
+			mut required := ''
+			if flag.required {
+				required = ' (required)'
+			}
+			base_indent := ' '.repeat(cli.base_indent_len)
+			description_indent := ' '.repeat(name_len - flag_name.len)
+			help += '$base_indent$flag_name$description_indent' +
+				pretty_description(flag.description + required, cli.base_indent_len + name_len) +
+				'\n'
+		}
+	}
+	if cmd.commands.len > 0 {
+		help += '\nCommands:\n'
+		for command in cmd.commands {
+			base_indent := ' '.repeat(cli.base_indent_len)
+			description_indent := ' '.repeat(name_len - command.name.len)
+			help += '$base_indent$command.name$description_indent' +
+				pretty_description(command.description, name_len) + '\n'
+		}
+	}
+	return help
+}
+
+// pretty_description resizes description text depending on terminal width.
+// Essentially, smart wrap-around
+fn pretty_description(s string, indent_len int) string {
+	width, _ := term.get_terminal_size()
+	// Don't prettify if the terminal is that small, it won't be pretty anyway.
+	if indent_len > width {
+		return s
+	}
+	indent := ' '.repeat(indent_len)
+	chars_per_line := width - indent_len
+	// Give us enough room, better a little bigger than smaller
+	mut acc := strings.new_builder(((s.len / chars_per_line) + 1) * (width + 1))
+	for k, line in s.split('\n') {
+		if k != 0 {
+			acc.write_string('\n$indent')
+		}
+		mut i := chars_per_line - 2
+		mut j := 0
+		for ; i < line.len; i += chars_per_line - 2 {
+			for line[i] != ` ` {
+				i--
+			}
+			// indent was already done the first iteration
+			if j != 0 {
+				acc.write_string(indent)
+			}
+			acc.writeln(line[j..i].trim_space())
+			j = i
+		}
+		// We need this even though it should never happen
+		if j != 0 {
+			acc.write_string(indent)
+		}
+		acc.write_string(line[j..].trim_space())
+	}
+	return acc.str()
+}
+
+fn max(a int, b int) int {
+	res := if a > b { a } else { b }
+	return res
+}
diff --git a/v_windows/v/vlib/cli/help_test.v b/v_windows/v/vlib/cli/help_test.v
new file mode 100644
index 0000000..62cb37e
--- /dev/null
+++ b/v_windows/v/vlib/cli/help_test.v
@@ -0,0 +1,65 @@
+module cli
+
+fn test_help_message() {
+	mut cmd := Command{
+		name: 'command'
+		description: 'description'
+		commands: [
+			Command{
+				name: 'sub'
+				description: 'subcommand'
+			},
+			Command{
+				name: 'sub2'
+				description: 'another subcommand'
+			},
+		]
+		flags: [
+			Flag{
+				flag: .string
+				name: 'str'
+				description: 'str flag'
+			},
+			Flag{
+				flag: .bool
+				name: 'bool'
+				description: 'bool flag'
+				abbrev: 'b'
+			},
+			Flag{
+				flag: .string
+				name: 'required'
+				abbrev: 'r'
+				required: true
+			},
+		]
+	}
+	assert cmd.help_message() == r'Usage: command [flags] [commands]
+
+description
+
+Flags:
+      -str            str flag
+  -b  -bool           bool flag
+  -r  -required       (required)
+
+Commands:
+  sub                 subcommand
+  sub2                another subcommand
+'
+
+	cmd.posix_mode = true
+	assert cmd.help_message() == r'Usage: command [flags] [commands]
+
+description
+
+Flags:
+      --str           str flag
+  -b  --bool          bool flag
+  -r  --required      (required)
+
+Commands:
+  sub                 subcommand
+  sub2                another subcommand
+'
+}
diff --git a/v_windows/v/vlib/cli/version.v b/v_windows/v/vlib/cli/version.v
new file mode 100644
index 0000000..0f3f583
--- /dev/null
+++ b/v_windows/v/vlib/cli/version.v
@@ -0,0 +1,25 @@
+module cli
+
+fn version_flag(with_abbrev bool) Flag {
+	sabbrev := if with_abbrev { 'v' } else { '' }
+	return Flag{
+		flag: .bool
+		name: 'version'
+		abbrev: sabbrev
+		description: 'Prints version information.'
+	}
+}
+
+fn version_cmd() Command {
+	return Command{
+		name: 'version'
+		description: 'Prints version information.'
+		execute: version_func
+	}
+}
+
+fn version_func(version_cmd Command) ? {
+	cmd := version_cmd.parent
+	version := '$cmd.name version $cmd.version'
+	println(version)
+}
-- 
cgit v1.2.3