tabry 0.1.5 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/tabry-bash +2 -29
  3. data/bin/tabry-generate-bash-complete +31 -0
  4. data/bin/tabry-help +1 -1
  5. data/bin/tabry-test-options +1 -1
  6. data/bin/tabry-test-parse +1 -1
  7. data/lib/tabry/cli/all_in_one.rb +98 -0
  8. data/lib/tabry/cli/arg_proxy.rb +4 -4
  9. data/lib/tabry/cli/builder.rb +9 -7
  10. data/lib/tabry/cli/internals.rb +1 -1
  11. data/lib/tabry/cli/util.rb +24 -10
  12. data/lib/tabry/config_builder/arg_or_flag_builder.rb +39 -0
  13. data/lib/tabry/config_builder/flagarg_builder.rb +14 -0
  14. data/lib/tabry/config_builder/generic_builder.rb +103 -0
  15. data/lib/tabry/config_builder/sub_builder.rb +27 -0
  16. data/lib/tabry/config_builder/top_level_builder.rb +31 -0
  17. data/lib/tabry/config_builder.rb +16 -0
  18. data/lib/tabry/models/config_list.rb +2 -2
  19. data/lib/tabry/models/config_string_hash.rb +2 -2
  20. data/lib/tabry/runner.rb +6 -2
  21. data/lib/tabry/shells/bash/wrapper.rb +38 -0
  22. data/lib/tabry/shells/bash.rb +46 -9
  23. data/sh/bash/tabry_bash.sh +1 -1
  24. data/sh/bash/tabry_bash_core.sh +3 -2
  25. data/spec/fixtures/config_builder/args.rb +49 -0
  26. data/spec/fixtures/config_builder/args.tabry +32 -0
  27. data/spec/fixtures/config_builder/args.yml +63 -0
  28. data/spec/fixtures/config_builder/defs.rb +33 -0
  29. data/spec/fixtures/config_builder/defs.tabry +21 -0
  30. data/spec/fixtures/config_builder/defs.yml +33 -0
  31. data/spec/fixtures/config_builder/flags.rb +26 -0
  32. data/spec/fixtures/config_builder/flags.tabry +13 -0
  33. data/spec/fixtures/config_builder/flags.yml +27 -0
  34. data/spec/fixtures/config_builder/subs.rb +34 -0
  35. data/spec/fixtures/config_builder/subs.tabry +25 -0
  36. data/spec/fixtures/config_builder/subs.yml +46 -0
  37. data/spec/fixtures/config_builder/underscoresdashes.rb +30 -0
  38. data/spec/fixtures/config_builder/underscoresdashes.tabry +18 -0
  39. data/spec/fixtures/config_builder/underscoresdashes.yml +39 -0
  40. data/spec/tabry/cli/all_in_one_spec.rb +141 -0
  41. data/spec/tabry/cli/arg_proxy_spec.rb +2 -2
  42. data/spec/tabry/cli/builder_spec.rb +5 -5
  43. data/spec/tabry/cli/util_spec.rb +125 -0
  44. data/spec/tabry/config_builder_spec.rb +64 -0
  45. data/spec/tabry/runner_spec.rb +1 -1
  46. data/spec/tabry/shell_splitter_spec.rb +7 -5
  47. data/spec/tabry/shells/bash_spec.rb +44 -0
  48. data/tabry.gemspec +1 -1
  49. metadata +31 -3
  50. 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: fe7c5c94f54f4d6fb603a73a8ec6b8364c63b6247a9dc3f095d4359fe96b5018
4
- data.tar.gz: 4a29c24624360099c44dfd5c8c1420ef701bb21ee6535dc1e7a3540315002811
3
+ metadata.gz: 4a76bb8b85fa94e61bf625e435711d8e3385f5aa50a87a7c410e4f252947ffea
4
+ data.tar.gz: '0632896bf92ca8b57a08db3b153e32aed0da12c71d9604d7260de2fba6fc9707'
5
5
  SHA512:
6
- metadata.gz: b9b58e549801135afe35686048bd3b84b76c9e41cb5ecd5dc49bff3a1c3ea3ac26f10a2187eca5775ce4aa2d02d46157c559cefc2d376267ff62ebcf630b721c
7
- data.tar.gz: a21e775881c48a511363cce6690e99cd13ccb32a3b829670bf049f3de63ab6e590b351a591717bb8137f112eb0e81514f548cefb8e3a0f4a79e5339118fafb33
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
- cmd_line, comp_point = ARGV
16
- cmd_name, args, last_arg = Tabry::ShellSplitter.split(cmd_line, comp_point)
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(config_name: config_name).parse(args)
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
@@ -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(config_name: config_name).options(args, last).inspect
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(config_name: config_name)
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
@@ -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
@@ -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 :config, :cli_class, :runner
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(config_name: config_name)
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, config: config, raw_args: raw_args,
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
 
@@ -3,7 +3,7 @@
3
3
  module Tabry
4
4
  module CLI
5
5
  Internals = Struct.new(
6
- :runner, :config_name, :config, :raw_args, :state, :met, :result,
6
+ :runner, :raw_args, :state, :met, :result,
7
7
  keyword_init: true
8
8
  )
9
9
  end
@@ -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
- backtick_with_process_status(*cmdline, **opts).first
39
+ backtick_with_status(*cmdline, valid_statuses: [0], **opts).first
39
40
  end
40
41
 
41
- def backtick_with_process_status(*cmdline, valid_statuses: [0], **opts)
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
- res = `#{cmdline}`
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
- warn "COMMAND FAILED with exit code #{status.exitstatus}: #{cmdline}"
49
- exit 1
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 open_web_page(url)
55
- command = RUBY_PLATFORM.include?("linux") ? "xdg-open" : "open"
56
- unless system("(%s %s 2>&1) >/dev/null", command, url)
57
- warn "WARNING: opening web page failed: #{make_cmdline("%s %s", command, url)}"
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,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "arg_or_flag_builder"
4
+
5
+ module Tabry
6
+ module ConfigBuilder
7
+ class FlagargBuilder < ArgOrFlagBuilder
8
+ def _init(*args)
9
+ super(*args)
10
+ _set :arg, true
11
+ end
12
+ end
13
+ end
14
+ 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
@@ -32,8 +32,8 @@ module Tabry
32
32
  to_a.[](*args)
33
33
  end
34
34
 
35
- def each(...)
36
- to_a.each(...)
35
+ def each(&blk)
36
+ to_a.each(&blk)
37
37
  end
38
38
 
39
39
  def length
@@ -28,8 +28,8 @@ module Tabry
28
28
  to_h.[](*args)
29
29
  end
30
30
 
31
- def each(...)
32
- to_h.each(...)
31
+ def each(&blk)
32
+ to_h.each(&blk)
33
33
  end
34
34
 
35
35
  def empty?
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(config_name:)
15
- @config = ConfigLoader.load(name: config_name)
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)