tabry 0.1.5 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/tabry-bash +2 -29
- data/bin/tabry-generate-bash-complete +31 -0
- data/bin/tabry-help +1 -1
- data/bin/tabry-test-options +1 -1
- data/bin/tabry-test-parse +1 -1
- data/lib/tabry/cli/all_in_one.rb +98 -0
- data/lib/tabry/cli/arg_proxy.rb +4 -4
- data/lib/tabry/cli/builder.rb +9 -7
- data/lib/tabry/cli/internals.rb +1 -1
- data/lib/tabry/cli/util.rb +24 -10
- data/lib/tabry/config_builder/arg_or_flag_builder.rb +39 -0
- data/lib/tabry/config_builder/flagarg_builder.rb +14 -0
- data/lib/tabry/config_builder/generic_builder.rb +103 -0
- data/lib/tabry/config_builder/sub_builder.rb +27 -0
- data/lib/tabry/config_builder/top_level_builder.rb +31 -0
- data/lib/tabry/config_builder.rb +16 -0
- data/lib/tabry/models/config_list.rb +2 -2
- data/lib/tabry/models/config_string_hash.rb +2 -2
- data/lib/tabry/runner.rb +6 -2
- data/lib/tabry/shells/bash/wrapper.rb +38 -0
- data/lib/tabry/shells/bash.rb +46 -9
- data/sh/bash/tabry_bash.sh +1 -1
- data/sh/bash/tabry_bash_core.sh +3 -2
- data/spec/fixtures/config_builder/args.rb +49 -0
- data/spec/fixtures/config_builder/args.tabry +32 -0
- data/spec/fixtures/config_builder/args.yml +63 -0
- data/spec/fixtures/config_builder/defs.rb +33 -0
- data/spec/fixtures/config_builder/defs.tabry +21 -0
- data/spec/fixtures/config_builder/defs.yml +33 -0
- data/spec/fixtures/config_builder/flags.rb +26 -0
- data/spec/fixtures/config_builder/flags.tabry +13 -0
- data/spec/fixtures/config_builder/flags.yml +27 -0
- data/spec/fixtures/config_builder/subs.rb +34 -0
- data/spec/fixtures/config_builder/subs.tabry +25 -0
- data/spec/fixtures/config_builder/subs.yml +46 -0
- data/spec/fixtures/config_builder/underscoresdashes.rb +30 -0
- data/spec/fixtures/config_builder/underscoresdashes.tabry +18 -0
- data/spec/fixtures/config_builder/underscoresdashes.yml +39 -0
- data/spec/tabry/cli/all_in_one_spec.rb +141 -0
- data/spec/tabry/cli/arg_proxy_spec.rb +2 -2
- data/spec/tabry/cli/builder_spec.rb +5 -5
- data/spec/tabry/cli/util_spec.rb +125 -0
- data/spec/tabry/config_builder_spec.rb +64 -0
- data/spec/tabry/runner_spec.rb +1 -1
- data/spec/tabry/shell_splitter_spec.rb +7 -5
- data/spec/tabry/shells/bash_spec.rb +44 -0
- data/tabry.gemspec +1 -1
- metadata +31 -3
- data/spec/lib/tabry/cli/util_spec.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a76bb8b85fa94e61bf625e435711d8e3385f5aa50a87a7c410e4f252947ffea
|
4
|
+
data.tar.gz: '0632896bf92ca8b57a08db3b153e32aed0da12c71d9604d7260de2fba6fc9707'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8420bd16657ebbe6e2f13c55a14e62fffab982b5d81f71d76c36ccdd97d36ee4a09ce7ccf1386c5b0b553681b48d7b3fdd90336fb72574d55ba426ca22200ed9
|
7
|
+
data.tar.gz: 1dcd973c7608319c73ae59ab8c516e0ccb54ec9c6bf909f13235e3be238e1059e93d2ba067d99a66b8de8fb5cca9c69e2cf861b1393aecbd9076580fbbdef37c
|
data/bin/tabry-bash
CHANGED
@@ -3,34 +3,7 @@
|
|
3
3
|
|
4
4
|
# Run by the tabry_bash_core.sh bash tab completion function to plug into Tabry
|
5
5
|
# to get completion options.
|
6
|
-
|
7
|
-
require "shellwords"
|
8
|
-
require "yaml"
|
9
|
-
require_relative "../lib/tabry/util"
|
10
|
-
require_relative "../lib/tabry/runner"
|
11
|
-
require_relative "../lib/tabry/shell_splitter"
|
12
|
-
|
13
6
|
# Bash-specific entrypoint, taking COMP_WORDS and COMP_CWORDS and returning possible options
|
14
7
|
|
15
|
-
|
16
|
-
|
17
|
-
opts = Tabry::Runner.new(config_name: cmd_name).options(args, last_arg)
|
18
|
-
|
19
|
-
if Tabry::Util.debug?
|
20
|
-
require "json"
|
21
|
-
$stderr.puts
|
22
|
-
warn "debug: got command line and comp_point: #{cmd_line.inspect}, #{comp_point}"
|
23
|
-
warn "using args: #{args.inspect}"
|
24
|
-
warn "using lastarg: #{last_arg.inspect}"
|
25
|
-
warn "results from Tabry#options(): #{opts.inspect}"
|
26
|
-
warn "--- end debug output ---"
|
27
|
-
end
|
28
|
-
|
29
|
-
normal_opts = opts.select { |t| t.is_a?(String) }
|
30
|
-
special_opts = opts.select { |t| t.is_a?(Symbol) }
|
31
|
-
|
32
|
-
puts normal_opts.map { |t| Shellwords.escape(t) }.join("\n")
|
33
|
-
if special_opts.any?
|
34
|
-
puts
|
35
|
-
puts special_opts.join("\n")
|
36
|
-
end
|
8
|
+
require_relative "../lib/tabry/shells/bash/wrapper"
|
9
|
+
Tabry::Bash::Wrapper.run(*ARGV)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "../lib/tabry/cli/all_in_one"
|
4
|
+
require_relative "../lib/tabry/shells/bash"
|
5
|
+
|
6
|
+
Tabry::CLI::AllInOne.run do
|
7
|
+
config(names_underscores_to_dashes: true) do
|
8
|
+
desc <<~DESC
|
9
|
+
Generates bash completion for the tabry relative to this script
|
10
|
+
for the given command name and tabry JSON/YML file. Generates
|
11
|
+
bash functions unique to the command name to avoid conflicting with
|
12
|
+
other potential tabry installations.
|
13
|
+
DESC
|
14
|
+
arg :command_name, "Name of the command, e.g. 'mycli'"
|
15
|
+
arg :tabry_json_path, "Path to the mycli.json or mycli.yml compiled tabry file"
|
16
|
+
flagarg "uniq-fn-id,i", <<~DESC
|
17
|
+
Unique identifier to use for the unique tabry completion bash functions
|
18
|
+
(default is the capitalized command name)
|
19
|
+
DESC
|
20
|
+
end
|
21
|
+
|
22
|
+
def main
|
23
|
+
puts Tabry::Shells::Bash.generate(
|
24
|
+
args.command_name,
|
25
|
+
args.tabry_json_path,
|
26
|
+
uniq_fn_id: flags.uniq_fn_id,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
data/bin/tabry-help
CHANGED
@@ -11,7 +11,7 @@ require_relative "../lib/tabry/runner"
|
|
11
11
|
raise "Usage: tabry-help config_name [arg1 [arg2]]..." unless ARGV.length >= 1
|
12
12
|
|
13
13
|
config_name, *args = ARGV
|
14
|
-
result = Tabry::Runner.new(
|
14
|
+
result = Tabry::Runner.new(config: config_name).parse(args)
|
15
15
|
usage = result.usage(config_name)
|
16
16
|
puts "(Usage info from tabry-help; running command directly may provide more help)\n\n"
|
17
17
|
puts usage
|
data/bin/tabry-test-options
CHANGED
@@ -6,4 +6,4 @@ require_relative "../lib/tabry/runner"
|
|
6
6
|
raise "Usage: tabry-test-options config_name arg1 [arg2]..." unless ARGV.length >= 1
|
7
7
|
|
8
8
|
config_name, *args, last = ARGV
|
9
|
-
puts Tabry::Runner.new(
|
9
|
+
puts Tabry::Runner.new(config: config_name).options(args, last).inspect
|
data/bin/tabry-test-parse
CHANGED
@@ -8,7 +8,7 @@ require_relative "../lib/tabry/runner"
|
|
8
8
|
raise "Usage: tabry-test-parse config_name arg1 [arg2]..." unless ARGV.length >= 1
|
9
9
|
|
10
10
|
config_name, *args = ARGV
|
11
|
-
runner = Tabry::Runner.new(
|
11
|
+
runner = Tabry::Runner.new(config: config_name)
|
12
12
|
result = runner.parse(args)
|
13
13
|
|
14
14
|
puts result.state
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "builder"
|
5
|
+
require_relative "../config_builder"
|
6
|
+
|
7
|
+
# Define a Tabry Config + CLI in one call to build or run. The block passed in should define
|
8
|
+
# your CLI methods and include a call to `config` with a tabry config or block that defines
|
9
|
+
# one (passed on to Tabry::ConfigBuilder)
|
10
|
+
module Tabry
|
11
|
+
module CLI
|
12
|
+
module AllInOne
|
13
|
+
class AllInOneBase < Base
|
14
|
+
def self.config(opts = {}, &blk)
|
15
|
+
require_relative "../models/config"
|
16
|
+
if opts.is_a?(Tabry::Models::Config)
|
17
|
+
conf = opts
|
18
|
+
else
|
19
|
+
require_relative "../config_builder"
|
20
|
+
conf = Tabry::ConfigBuilder.build(**opts, &blk)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set it in the CLI class itself so AllInOne.build can find it
|
24
|
+
instance_variable_set(:@tabry_all_in_one_config, conf)
|
25
|
+
|
26
|
+
# Hack to avoid processing block any more if only running completion
|
27
|
+
# That way you an have expensive requires after the include
|
28
|
+
if (ARGV.first == "completion") && ARGV.length == 3 && ARGV[2].to_s =~ /^[0-9]+$/
|
29
|
+
throw :run_completion, true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates and runs a CLI whose only purpose is to create a completion
|
35
|
+
# wrapper for another program
|
36
|
+
def self.completion_only(completion_conf = nil, **opts, &cmd_conf_blk)
|
37
|
+
completion_conf ||= Tabry::ConfigBuilder.build(**opts, &cmd_conf_blk)
|
38
|
+
cmd_name = completion_conf.cmd or raise "cmd is mandatory for completion_only configs"
|
39
|
+
|
40
|
+
cli_class = Class.new(Tabry::CLI::Base)
|
41
|
+
cli_conf = Tabry::ConfigBuilder.build { completion }
|
42
|
+
define_completion_methods(cli_class, completion_conf, cmd_name: cmd_name)
|
43
|
+
Tabry::CLI::Builder.new(cli_conf, cli_class).run(ARGV)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.define_completion_methods(cli_class, config, cmd_name: nil)
|
47
|
+
cli_class.module_eval do
|
48
|
+
define_method :completion__bash do
|
49
|
+
require_relative "../shells/bash"
|
50
|
+
Kernel.puts Tabry::Shells::Bash.generate_self(cmd_name: cmd_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
define_method :completion do
|
54
|
+
require_relative "../shells/bash/wrapper"
|
55
|
+
Tabry::Bash::Wrapper.run(args.cmd_line, args.comp_point, config: config)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Instead of passing in config, you can also run `config` with a block or a config option
|
61
|
+
# in inside blk. Doing this will also enable the "run completion fast" (see config method)
|
62
|
+
def self.build(cli: nil, config: nil, &blk)
|
63
|
+
# def self.build(cli: nil, config: nil, &blk)
|
64
|
+
cli ||= Class.new(AllInOneBase)
|
65
|
+
|
66
|
+
# If block given. run it to define the CLI methods and/or setting the config
|
67
|
+
# with a call to AllInOneBase.config
|
68
|
+
if blk
|
69
|
+
run_completion = catch(:run_completion) do
|
70
|
+
cli.module_eval(&blk)
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
config ||= cli.instance_variable_get(:@tabry_all_in_one_config)
|
76
|
+
|
77
|
+
# If we recognize command is going to be a completion command, fast track and
|
78
|
+
# run completion now
|
79
|
+
if run_completion
|
80
|
+
require_relative "../shells/bash/wrapper"
|
81
|
+
Tabry::Bash::Wrapper.run(ARGV[1], ARGV[2], config: config)
|
82
|
+
end
|
83
|
+
|
84
|
+
# If we recognize there is a "completion" subcommand, add completion
|
85
|
+
# methods -- if not already defined by caller in the block
|
86
|
+
if config.main.subs.by_name["completion"] && !cli.instance_methods.include?(:completion__bash)
|
87
|
+
define_completion_methods(cli, config)
|
88
|
+
end
|
89
|
+
|
90
|
+
Tabry::CLI::Builder.new(config, cli)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.run(**kwargs, &blk)
|
94
|
+
build(**kwargs, &blk).run(ARGV)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/tabry/cli/arg_proxy.rb
CHANGED
@@ -15,8 +15,8 @@ module Tabry
|
|
15
15
|
@reqd_arg_proxy = ArgProxy.new(args, named_args, reqd: true) unless @reqd
|
16
16
|
end
|
17
17
|
|
18
|
-
def each(
|
19
|
-
@args.each(
|
18
|
+
def each(&blk)
|
19
|
+
@args.each(&blk)
|
20
20
|
end
|
21
21
|
|
22
22
|
def [](key)
|
@@ -24,14 +24,14 @@ module Tabry
|
|
24
24
|
res = @args[key]
|
25
25
|
if @reqd && !res
|
26
26
|
warn "FATAL: Missing required argument number #{key + 1}"
|
27
|
-
exit 1
|
27
|
+
Kernel.exit 1
|
28
28
|
end
|
29
29
|
else
|
30
30
|
key = key.to_s
|
31
31
|
res = @named_args[key]
|
32
32
|
if @reqd && !res
|
33
33
|
warn "FATAL: Missing required argument #{key}"
|
34
|
-
exit 1
|
34
|
+
Kernel.exit 1
|
35
35
|
end
|
36
36
|
end
|
37
37
|
res
|
data/lib/tabry/cli/builder.rb
CHANGED
@@ -4,14 +4,16 @@ require_relative "../runner"
|
|
4
4
|
require_relative "../util"
|
5
5
|
require_relative "internals"
|
6
6
|
|
7
|
+
# Responsible for building the CLI object, making a Runner to parse the
|
8
|
+
# arguments, and running the proper action method of the CLI object.
|
7
9
|
module Tabry
|
8
10
|
module CLI
|
9
11
|
class Builder
|
10
|
-
attr_reader :
|
12
|
+
attr_reader :cli_class, :runner
|
11
13
|
|
12
14
|
def initialize(config_name, cli_class)
|
13
15
|
@cli_class = cli_class
|
14
|
-
@runner = Tabry::Runner.new(
|
16
|
+
@runner = Tabry::Runner.new(config: config_name)
|
15
17
|
end
|
16
18
|
|
17
19
|
DISALLOWED_SUBCOMMAND_NAMES = %w[args flags internals].freeze
|
@@ -28,7 +30,7 @@ module Tabry
|
|
28
30
|
::Tabry::Util.debug "named_args: #{result.named_args.inspect}"
|
29
31
|
|
30
32
|
internals = Internals.new(
|
31
|
-
runner: runner,
|
33
|
+
runner: runner, raw_args: raw_args,
|
32
34
|
state: state, met: met, result: result
|
33
35
|
)
|
34
36
|
|
@@ -43,12 +45,12 @@ module Tabry
|
|
43
45
|
def check_for_correct_usage(result)
|
44
46
|
if result.help?
|
45
47
|
puts result.usage(File.basename($0))
|
46
|
-
exit 0
|
48
|
+
Kernel.exit 0
|
47
49
|
elsif result.invalid_usage_reason
|
48
50
|
puts "Invalid usage: #{result.invalid_usage_reason}"
|
49
51
|
puts
|
50
52
|
puts result.usage(File.basename($0))
|
51
|
-
exit(1)
|
53
|
+
Kernel.exit(1)
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
@@ -63,7 +65,7 @@ module Tabry
|
|
63
65
|
def get_cli_object_and_met(cli, met, internals)
|
64
66
|
if DISALLOWED_SUBCOMMAND_NAMES.include?(met)
|
65
67
|
warn %(FATAL: Tabry does not support top-level subcommands named: #{DISALLOWED_SUBCOMMAND_NAMES.join(",")})
|
66
|
-
exit 1
|
68
|
+
Kernel.exit 1
|
67
69
|
end
|
68
70
|
|
69
71
|
return [cli, "main"] if met.to_s == ""
|
@@ -90,7 +92,7 @@ module Tabry
|
|
90
92
|
run_hooks(cli, met, :@after_actions)
|
91
93
|
else
|
92
94
|
warn %(FATAL: CLI does not support command #{met})
|
93
|
-
exit 1
|
95
|
+
Kernel.exit 1
|
94
96
|
end
|
95
97
|
end
|
96
98
|
|
data/lib/tabry/cli/internals.rb
CHANGED
data/lib/tabry/cli/util.rb
CHANGED
@@ -8,7 +8,7 @@ module Tabry
|
|
8
8
|
module Util
|
9
9
|
module_function
|
10
10
|
|
11
|
-
def make_cmdline(cmdline, *args, echo: false, echo_only: false)
|
11
|
+
def make_cmdline(cmdline, *args, echo: false, echo_only: false, merge_stderr: false)
|
12
12
|
# Allow to pass in an array, or varargs:
|
13
13
|
args = args.first if args.length == 1 && args.first.is_a?(Array)
|
14
14
|
|
@@ -21,6 +21,7 @@ module Tabry
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
cmdline = cmdline % args
|
24
|
+
cmdline = "{ #{cmdline} ;} 2>&1" if merge_stderr
|
24
25
|
|
25
26
|
warn cmdline if echo || echo_only
|
26
27
|
return nil if echo_only
|
@@ -35,26 +36,39 @@ module Tabry
|
|
35
36
|
|
36
37
|
# TODO: would be nice to get separate STDERR and STDOUT
|
37
38
|
def backtick_or_die(*cmdline, **opts)
|
38
|
-
|
39
|
+
backtick_with_status(*cmdline, valid_statuses: [0], **opts).first
|
39
40
|
end
|
40
41
|
|
41
|
-
def
|
42
|
+
def backtick_with_status(*cmdline, valid_statuses: nil, extra_error_message: nil, **opts)
|
42
43
|
cmdline = make_cmdline(*cmdline, **opts)
|
43
44
|
return [nil, nil] unless cmdline
|
44
45
|
|
45
|
-
|
46
|
+
enoent_error = false
|
47
|
+
begin
|
48
|
+
res = `#{cmdline}`
|
49
|
+
rescue Errno::ENOENT
|
50
|
+
enoent_error = true
|
51
|
+
end
|
52
|
+
|
46
53
|
status = $?
|
47
54
|
if valid_statuses && !valid_statuses.include?(status.exitstatus)
|
48
|
-
|
49
|
-
|
55
|
+
msg = "COMMAND FAILED with exit code #{status.exitstatus}"
|
56
|
+
msg += " (command does not exist)" if enoent_error
|
57
|
+
Kernel.warn "#{msg}: #{cmdline}"
|
58
|
+
Kernel.warn "Command output:\n#{res}"
|
59
|
+
Kernel.warn extra_error_message if extra_error_message
|
60
|
+
Kernel.exit 1
|
50
61
|
end
|
51
62
|
[res, status]
|
52
63
|
end
|
53
64
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
65
|
+
def open_command
|
66
|
+
RUBY_PLATFORM.include?("linux") ? "xdg-open" : "open"
|
67
|
+
end
|
68
|
+
|
69
|
+
def open_web_page(url_or_urls)
|
70
|
+
unless system("(%s %s 2>&1) >/dev/null", open_command, url_or_urls)
|
71
|
+
warn "WARNING: opening web page failed: #{make_cmdline("%s %s", open_command, url)}"
|
58
72
|
end
|
59
73
|
end
|
60
74
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "generic_builder"
|
4
|
+
|
5
|
+
module Tabry
|
6
|
+
module ConfigBuilder
|
7
|
+
class ArgOrFlagBuilder < GenericBuilder
|
8
|
+
init_setters :name, :description, split_name_to_aliases: true
|
9
|
+
simple_setter :desc, :description
|
10
|
+
simple_setter :title
|
11
|
+
|
12
|
+
def _include(inc)
|
13
|
+
_append :options, { "type" => "include", "value" => inc }
|
14
|
+
end
|
15
|
+
|
16
|
+
def opts(opt_hashes)
|
17
|
+
_append :options, *opt_hashes
|
18
|
+
end
|
19
|
+
|
20
|
+
def shell(cmd)
|
21
|
+
[{ "type" => "shell", "value" => cmd.to_s }]
|
22
|
+
end
|
23
|
+
|
24
|
+
def file
|
25
|
+
[{ "type" => "file" }]
|
26
|
+
end
|
27
|
+
|
28
|
+
def dir
|
29
|
+
[{ "type" => "dir" }]
|
30
|
+
end
|
31
|
+
|
32
|
+
def const(opts)
|
33
|
+
[opts].flatten.map do |opt|
|
34
|
+
[{ "type" => "const", "value" => opt.to_s }]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GenericBuilder
|
4
|
+
attr_reader :_opts
|
5
|
+
|
6
|
+
private_class_method :new
|
7
|
+
def self.build(opts = {}, *args, &blk)
|
8
|
+
builder = new(opts, *args)
|
9
|
+
builder.instance_eval(&blk) if blk
|
10
|
+
builder._obj.compact
|
11
|
+
end
|
12
|
+
|
13
|
+
# Should be used instead of MyBuilder.build to pass in opts
|
14
|
+
def _build(builder, *args, &blk)
|
15
|
+
builder.build(_opts, *args, &blk)
|
16
|
+
end
|
17
|
+
|
18
|
+
def _obj
|
19
|
+
@_obj ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def _append(list_name, *value_or_values)
|
23
|
+
_obj[list_name.to_s] ||= []
|
24
|
+
_obj[list_name.to_s].concat([value_or_values].flatten)
|
25
|
+
value_or_values
|
26
|
+
end
|
27
|
+
|
28
|
+
def _set_hash(hash_name, key, value)
|
29
|
+
_obj[hash_name.to_s] ||= {}
|
30
|
+
_obj[hash_name.to_s][key.to_s] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def _set(key, val)
|
34
|
+
val = val.chomp if val.is_a?(String)
|
35
|
+
val = val.to_s if val.is_a?(Symbol)
|
36
|
+
if _opts[:names_underscores_to_dashes] && key.to_s == "name"
|
37
|
+
val = val.gsub("_", "-") if val.is_a?(String)
|
38
|
+
val = val.map { |name| name.to_s.gsub("_", "-") } if val.is_a?(Array)
|
39
|
+
end
|
40
|
+
_obj[key.to_s] = val
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(opts, *args)
|
44
|
+
@_opts = opts
|
45
|
+
includes, not_includes = args.partition { |arg| (arg.is_a?(Symbol) || arg.is_a?(String)) && arg.to_s.start_with?("@") }
|
46
|
+
includes.each { |arg| include(arg) }
|
47
|
+
_init(*not_includes) if defined?(:_init)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.simple_setter(name, key = nil)
|
51
|
+
define_method name do |value|
|
52
|
+
value = value.chomp if value.is_a?(String)
|
53
|
+
_set (key || name), value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.builder_appender(name, key, builder, merge: {})
|
58
|
+
define_method name.to_sym do |*args, &blk|
|
59
|
+
value = _build(builder, *args, &blk).merge(merge.transform_keys(&:to_s))
|
60
|
+
_append key, value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def include(inc)
|
65
|
+
inc_str = inc.to_s
|
66
|
+
raise "include must be a start with @" unless inc_str.start_with?("@")
|
67
|
+
|
68
|
+
include_name = inc_str[1..]
|
69
|
+
if _opts[:names_underscores_to_dashes]
|
70
|
+
include_name = include_name.gsub("_", "-")
|
71
|
+
end
|
72
|
+
|
73
|
+
_include include_name
|
74
|
+
end
|
75
|
+
|
76
|
+
def _include(_inc)
|
77
|
+
raise "_include not implemented for #{self.class}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def _augment_list_item(list_name, item)
|
81
|
+
item = _obj[list_name].find { |i| i.equal?(item) }
|
82
|
+
yield item
|
83
|
+
item
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.init_setters(*setters, split_name_to_aliases: false)
|
87
|
+
define_method :_init do |*args|
|
88
|
+
args.zip(setters) do |arg, setter|
|
89
|
+
_set setter, arg
|
90
|
+
_split_name_to_aliases if split_name_to_aliases
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def _split_name_to_aliases
|
96
|
+
name = _obj["name"]
|
97
|
+
if name
|
98
|
+
name = name.split(/[ ,]{1,2}/) unless name.is_a?(Array)
|
99
|
+
_obj["name"], *aliases = name
|
100
|
+
aliases.each { |a| _append :aliases, a }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "arg_or_flag_builder"
|
4
|
+
require_relative "flagarg_builder"
|
5
|
+
require_relative "generic_builder"
|
6
|
+
|
7
|
+
module Tabry
|
8
|
+
module ConfigBuilder
|
9
|
+
class SubBuilder < GenericBuilder
|
10
|
+
init_setters :name, :description, split_name_to_aliases: true
|
11
|
+
simple_setter :desc, :description
|
12
|
+
builder_appender :arg, :args, ArgOrFlagBuilder
|
13
|
+
builder_appender :opt_arg, :args, ArgOrFlagBuilder, merge: { optional: true }
|
14
|
+
builder_appender :varargs, :args, ArgOrFlagBuilder, merge: { varargs: true }
|
15
|
+
builder_appender :opt_varargs, :args, ArgOrFlagBuilder, merge: { optional: true, varargs: true }
|
16
|
+
builder_appender :sub, :subs, SubBuilder
|
17
|
+
builder_appender :flag, :flags, ArgOrFlagBuilder
|
18
|
+
builder_appender :flagarg, :flags, FlagargBuilder
|
19
|
+
|
20
|
+
def _include(inc)
|
21
|
+
%i[args flags subs].each do |thing_type|
|
22
|
+
_append(thing_type, { "include" => inc })
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "arg_or_flag_builder"
|
4
|
+
require_relative "sub_builder"
|
5
|
+
|
6
|
+
module Tabry
|
7
|
+
module ConfigBuilder
|
8
|
+
class TopLevelBuilder < SubBuilder
|
9
|
+
simple_setter :cmd
|
10
|
+
|
11
|
+
def defargs(name, &blk)
|
12
|
+
name = name.to_s.gsub(/^@/, "")
|
13
|
+
_set_hash :arg_includes, name, _build(SubBuilder, &blk)
|
14
|
+
end
|
15
|
+
|
16
|
+
def defopts(name, &blk)
|
17
|
+
name = name.to_s.gsub(/^@/, "")
|
18
|
+
_set_hash :option_includes, name, _build(ArgOrFlagBuilder, &blk)["options"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def completion
|
22
|
+
sub :completion do
|
23
|
+
desc "Get tab completion shell config"
|
24
|
+
sub :bash, "Get tab completion for bash or zsh"
|
25
|
+
arg :cmd_line, "(for internal usage, when used instead of subcommand) full command line for getting completion options"
|
26
|
+
arg :comp_point, "(for internal usage, when used instead of subcommand) comp point"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "config_builder/top_level_builder"
|
4
|
+
require_relative "models/config"
|
5
|
+
|
6
|
+
module Tabry
|
7
|
+
module ConfigBuilder
|
8
|
+
def self.build(names_underscores_to_dashes: false, &blk)
|
9
|
+
opts = { names_underscores_to_dashes: names_underscores_to_dashes }
|
10
|
+
conf = TopLevelBuilder.build(opts, &blk)
|
11
|
+
top_level = %w[cmd arg_includes option_includes]
|
12
|
+
conf = conf.slice(*top_level).merge("main" => conf.reject { |k, _v| top_level.include?(k) })
|
13
|
+
Tabry::Models::Config.new(raw: conf)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/tabry/runner.rb
CHANGED
@@ -11,8 +11,12 @@ module Tabry
|
|
11
11
|
class Runner
|
12
12
|
attr_reader :config
|
13
13
|
|
14
|
-
def initialize(
|
15
|
-
@config =
|
14
|
+
def initialize(config:)
|
15
|
+
@config = if config.is_a?(Tabry::Models::Config)
|
16
|
+
config
|
17
|
+
else
|
18
|
+
ConfigLoader.load(name: config)
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
def options(args, last = nil)
|