tabry 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +23 -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: 5d76e2e99f45b1f9b964d21f0ed8820d37b973c358b032a97c3d5314afdae8a1
|
4
|
+
data.tar.gz: e84a7977c95fc91282291d1406ffc2e577f4c23a140e69b05f3b6fe10da4f85d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1a4d514766abadeee72d0f1db0e1de14c0bf1bc26964911460adffaa716f6a6dd9f36604632274b6a4772a3d734903af0543c8689257b214a439679a1ebb8e3
|
7
|
+
data.tar.gz: 23351f80640e72147ea32638327e99891aa4b46f8fedee2551ecdcadd2fb63102f6c505b430882171cd736320baac633dc7f93a26f9ccb89dfdd232c4bf6c833
|
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,38 @@ 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, **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.exit 1
|
50
60
|
end
|
51
61
|
[res, status]
|
52
62
|
end
|
53
63
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
64
|
+
def open_command
|
65
|
+
RUBY_PLATFORM.include?("linux") ? "xdg-open" : "open"
|
66
|
+
end
|
67
|
+
|
68
|
+
def open_web_page(url_or_urls)
|
69
|
+
unless system("(%s %s 2>&1) >/dev/null", open_command, url_or_urls)
|
70
|
+
warn "WARNING: opening web page failed: #{make_cmdline("%s %s", open_command, url)}"
|
58
71
|
end
|
59
72
|
end
|
60
73
|
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)
|