tabry 0.1.0
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 +7 -0
- data/bin/tabry-bash +48 -0
- data/bin/tabry-help +17 -0
- data/bin/tabry-test-options +9 -0
- data/bin/tabry-test-parse +20 -0
- data/lib/tabry/cli/arg_proxy.rb +71 -0
- data/lib/tabry/cli/base.rb +34 -0
- data/lib/tabry/cli/builder.rb +104 -0
- data/lib/tabry/cli/flag_proxy.rb +45 -0
- data/lib/tabry/cli/internals.rb +10 -0
- data/lib/tabry/cli/util/config.rb +48 -0
- data/lib/tabry/cli/util.rb +51 -0
- data/lib/tabry/config_loader.rb +55 -0
- data/lib/tabry/core_ext/string/colors.rb +91 -0
- data/lib/tabry/machine.rb +124 -0
- data/lib/tabry/models/arg.rb +43 -0
- data/lib/tabry/models/arg_base.rb +10 -0
- data/lib/tabry/models/arg_include.rb +9 -0
- data/lib/tabry/models/arg_includes.rb +14 -0
- data/lib/tabry/models/args_list.rb +31 -0
- data/lib/tabry/models/config.rb +44 -0
- data/lib/tabry/models/config_error.rb +8 -0
- data/lib/tabry/models/config_list.rb +48 -0
- data/lib/tabry/models/config_object.rb +78 -0
- data/lib/tabry/models/config_string_hash.rb +44 -0
- data/lib/tabry/models/const_option.rb +17 -0
- data/lib/tabry/models/dir_option.rb +25 -0
- data/lib/tabry/models/file_option.rb +25 -0
- data/lib/tabry/models/flag.rb +55 -0
- data/lib/tabry/models/flags_list.rb +47 -0
- data/lib/tabry/models/include_arg.rb +18 -0
- data/lib/tabry/models/include_flag.rb +18 -0
- data/lib/tabry/models/include_option.rb +22 -0
- data/lib/tabry/models/include_sub.rb +18 -0
- data/lib/tabry/models/option.rb +33 -0
- data/lib/tabry/models/option_base.rb +20 -0
- data/lib/tabry/models/option_includes.rb +14 -0
- data/lib/tabry/models/options_list.rb +18 -0
- data/lib/tabry/models/shell_option.rb +13 -0
- data/lib/tabry/models/sub.rb +59 -0
- data/lib/tabry/models/subs_list.rb +28 -0
- data/lib/tabry/options_finder.rb +87 -0
- data/lib/tabry/result.rb +110 -0
- data/lib/tabry/runner.rb +27 -0
- data/lib/tabry/state.rb +14 -0
- data/lib/tabry/usage_generator.rb +137 -0
- data/lib/tabry/util.rb +15 -0
- data/sh/tabry_bash.sh +61 -0
- data/sh/tabry_bash_help.sh +7 -0
- data/spec/fixtures/basiccli.json +1 -0
- data/spec/fixtures/basiccli.tabry +5 -0
- data/spec/fixtures/basiccli2.tabry +5 -0
- data/spec/fixtures/basiccli2.yml +7 -0
- data/spec/fixtures/vehicles.tabry +60 -0
- data/spec/fixtures/vehicles.yaml +135 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/tabry/cli/arg_proxy_spec.rb +76 -0
- data/spec/tabry/cli/builder_spec.rb +226 -0
- data/spec/tabry/config_loader_spec.rb +69 -0
- data/spec/tabry/machine_spec.rb +109 -0
- data/spec/tabry/models/args_list_spec.rb +36 -0
- data/spec/tabry/models/config_spec.rb +62 -0
- data/spec/tabry/models/const_option_spec.rb +17 -0
- data/spec/tabry/models/dir_option_spec.rb +16 -0
- data/spec/tabry/models/file_option_spec.rb +16 -0
- data/spec/tabry/models/options_list_spec.rb +47 -0
- data/spec/tabry/models/shell_option_spec.rb +19 -0
- data/spec/tabry/models/subs_list_spec.rb +24 -0
- data/spec/tabry/options_finder_spec.rb +91 -0
- data/spec/tabry/result_spec.rb +236 -0
- data/spec/tabry/runner_spec.rb +35 -0
- data/spec/tabry/usage_generator_spec.rb +67 -0
- data/tabry.gemspec +27 -0
- metadata +189 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8531aa56c6c59a5d678e280ccb39fa8fd891aadac1d4fd194d73cacee20aaa47
|
4
|
+
data.tar.gz: ab532e18804f52824ccebf21667aa8da6416324eaa428d58140b758e22160b6a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 62d4481040a2c2e06e5f16e0c5ef559fddf4cb6330d61d9962af8295c940d56c40bce23df662dbb168c83c48ed929fe30e1f2bf7e91ddc6fda06595c9576b63c
|
7
|
+
data.tar.gz: 1447eb824ec57c40b4dcc4eaeaefbd1488f514bd10535c7035f3439558f03a1368c1cf1cc7674804d2976ab9dadede3d0a818ff9e87e140cd58f67e983026ffb
|
data/bin/tabry-bash
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "shellwords"
|
5
|
+
require "yaml"
|
6
|
+
require_relative "../lib/tabry/util"
|
7
|
+
require_relative "../lib/tabry/runner"
|
8
|
+
|
9
|
+
# Bash-specific entrypoint, taking COMP_WORDS and COMP_CWORDS and returning possible options
|
10
|
+
|
11
|
+
COMP_POINT_SENTINEL = "\uFFFF"
|
12
|
+
|
13
|
+
# TODO: in weird scenarios this acts weird: namely, special shell operators like <(ls), $$((1 + 1))
|
14
|
+
# Also it crashed on unbalanced quotes, like: foo "bar<TAB>
|
15
|
+
# however, this will handle the common scenarios of escaping with quotes, single quotes, and backslashes
|
16
|
+
# Split up args and put the argument that comp_point is in in the `last_arg` variable.
|
17
|
+
# Just cutting off everything after comp_point might have worked, although
|
18
|
+
# maybe we wanted the whole arg? Not sure this is the best.
|
19
|
+
cmd_line, comp_point = ARGV
|
20
|
+
cmd_line = cmd_line.dup
|
21
|
+
cmd_line[comp_point.to_i...comp_point.to_i] = COMP_POINT_SENTINEL
|
22
|
+
cmd, *all_args = Shellwords.split(cmd_line)
|
23
|
+
last_arg_index = all_args.index { |arg| arg.include?(COMP_POINT_SENTINEL) }
|
24
|
+
args = all_args[0..last_arg_index]
|
25
|
+
last_arg = args.pop.gsub! COMP_POINT_SENTINEL, ""
|
26
|
+
|
27
|
+
cmd_name = cmd.gsub(%r{.*/}, "")
|
28
|
+
|
29
|
+
opts = Tabry::Runner.new(config_name: cmd_name).options(args, last_arg)
|
30
|
+
|
31
|
+
if Tabry::Util.debug?
|
32
|
+
require "json"
|
33
|
+
$stderr.puts
|
34
|
+
warn "debug: got command line and comp_point: #{cmd_line.inspect}, #{comp_point}"
|
35
|
+
warn "using args: #{args.inspect}"
|
36
|
+
warn "using lastarg: #{last_arg.inspect}"
|
37
|
+
warn "results from Tabry#options(): #{opts.inspect}"
|
38
|
+
warn "--- end debug output ---"
|
39
|
+
end
|
40
|
+
|
41
|
+
normal_opts = opts.select { |t| t.is_a?(String) }
|
42
|
+
special_opts = opts.select { |t| t.is_a?(Symbol) }
|
43
|
+
|
44
|
+
puts normal_opts.map { |t| Shellwords.escape(t) }.join("\n")
|
45
|
+
if special_opts.any?
|
46
|
+
puts
|
47
|
+
puts special_opts.join("\n")
|
48
|
+
end
|
data/bin/tabry-help
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/tabry/runner"
|
5
|
+
|
6
|
+
# Show usage information stored in a tabry configuration.
|
7
|
+
#
|
8
|
+
# For ease of use, source sh/tabry_bash_help.sh so you can just do
|
9
|
+
# "help mycmd"
|
10
|
+
|
11
|
+
raise "Usage: tabry-help config_name [arg1 [arg2]]..." unless ARGV.length >= 1
|
12
|
+
|
13
|
+
config_name, *args = ARGV
|
14
|
+
result = Tabry::Runner.new(config_name: config_name).parse(args)
|
15
|
+
usage = result.usage(config_name)
|
16
|
+
puts "(Usage info from tabry-help; running command directly may provide more help)\n\n"
|
17
|
+
puts usage
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/tabry/runner"
|
5
|
+
|
6
|
+
raise "Usage: tabry-test-options config_name arg1 [arg2]..." unless ARGV.length >= 1
|
7
|
+
|
8
|
+
config_name, *args, last = ARGV
|
9
|
+
puts Tabry::Runner.new(config_name: config_name).options(args, last).inspect
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/tabry/runner"
|
5
|
+
|
6
|
+
# Test parsing of arguments for a config
|
7
|
+
|
8
|
+
raise "Usage: tabry-test-parse config_name arg1 [arg2]..." unless ARGV.length >= 1
|
9
|
+
|
10
|
+
config_name, *args = ARGV
|
11
|
+
runner = Tabry::Runner.new(config_name: config_name)
|
12
|
+
result = runner.parse(args)
|
13
|
+
|
14
|
+
puts result.state
|
15
|
+
if result.invalid_usage_reason
|
16
|
+
puts "Invalid usage: #{result.invalid_usage_reason}"
|
17
|
+
else
|
18
|
+
puts "Valid usage"
|
19
|
+
end
|
20
|
+
puts "Named args: #{result.named_args.inspect}"
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "flag_proxy"
|
4
|
+
|
5
|
+
module Tabry
|
6
|
+
module CLI
|
7
|
+
class ArgProxy
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(args, named_args, reqd: false)
|
11
|
+
@args = args
|
12
|
+
@named_args = FlagProxy.new(named_args)
|
13
|
+
@raw_named_args = named_args
|
14
|
+
@reqd = reqd
|
15
|
+
@reqd_arg_proxy = ArgProxy.new(args, named_args, reqd: true) unless @reqd
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(...)
|
19
|
+
@args.each(...)
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
if key.is_a?(Integer)
|
24
|
+
res = @args[key]
|
25
|
+
if @reqd && !res
|
26
|
+
warn "FATAL: Missing required argument number #{key + 1}"
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
else
|
30
|
+
key = key.to_s
|
31
|
+
res = @named_args[key]
|
32
|
+
if @reqd && !res
|
33
|
+
warn "FATAL: Missing required argument #{key}"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
res
|
38
|
+
end
|
39
|
+
|
40
|
+
def slice(*keys)
|
41
|
+
[keys].flatten.each_with_object({}) do |key, result_hash|
|
42
|
+
result_hash[key] = self[key]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def <=>(*args)
|
47
|
+
@args.<=>(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_missing(met)
|
51
|
+
self[met]
|
52
|
+
end
|
53
|
+
|
54
|
+
def respond_to_missing?(*_args)
|
55
|
+
true # Anything could be an arg name that we haven't set
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
"ArgProxy: #{@args.inspect}, #{@raw_named_args.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
"ArgProxy: #{@args}, #{@raw_named_args.inspect}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def reqd
|
67
|
+
@reqd_arg_proxy or raise "no reqd.reqd"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "arg_proxy"
|
4
|
+
require_relative "flag_proxy"
|
5
|
+
|
6
|
+
module Tabry
|
7
|
+
module CLI
|
8
|
+
class Base
|
9
|
+
attr_reader :args, :flags, :internals
|
10
|
+
|
11
|
+
def initialize(flags, args, named_args, internals)
|
12
|
+
@args = ArgProxy.new(args, named_args)
|
13
|
+
@flags = FlagProxy.new(flags)
|
14
|
+
@internals = internals
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.sub_route(prefix, cli_class)
|
18
|
+
(@sub_route_clis ||= {})[prefix.to_s] = cli_class
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.after_action(*method_names, only: nil, except: nil, &blk)
|
22
|
+
[*method_names, blk].compact.each do |met|
|
23
|
+
(@after_actions ||= []) << [met, { only: only, except: except }]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.before_action(*method_names, only: nil, except: nil, &blk)
|
28
|
+
[*method_names, blk].compact.each do |met|
|
29
|
+
(@before_actions ||= []) << [met, { only: only, except: except }]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../runner"
|
4
|
+
require_relative "../util"
|
5
|
+
require_relative "internals"
|
6
|
+
|
7
|
+
module Tabry
|
8
|
+
module CLI
|
9
|
+
class Builder
|
10
|
+
attr_reader :config, :cli_class, :runner
|
11
|
+
|
12
|
+
def initialize(config_name, cli_class)
|
13
|
+
@cli_class = cli_class
|
14
|
+
@runner = Tabry::Runner.new(config_name: config_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
DISALLOWED_SUBCOMMAND_NAMES = %w[args flags internals].freeze
|
18
|
+
|
19
|
+
def run(raw_args)
|
20
|
+
result = runner.parse(raw_args)
|
21
|
+
check_for_correct_usage(result)
|
22
|
+
|
23
|
+
state = result.state
|
24
|
+
|
25
|
+
met = state.subcommand_stack.join("__").gsub("-", "_")
|
26
|
+
|
27
|
+
::Tabry::Util.debug "met: #{met.inspect}"
|
28
|
+
::Tabry::Util.debug "named_args: #{result.named_args.inspect}"
|
29
|
+
|
30
|
+
internals = Internals.new(
|
31
|
+
runner: runner, config: config, raw_args: raw_args,
|
32
|
+
state: state, met: met, result: result
|
33
|
+
)
|
34
|
+
|
35
|
+
cli = instantiate_cli(@cli_class, internals)
|
36
|
+
actual_cli, actual_met = get_cli_object_and_met(cli, met, internals)
|
37
|
+
|
38
|
+
cli_send_met(actual_cli, actual_met)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def check_for_correct_usage(result)
|
44
|
+
if result.help?
|
45
|
+
puts result.usage(File.basename($0))
|
46
|
+
exit 0
|
47
|
+
elsif result.invalid_usage_reason
|
48
|
+
puts "Invalid usage: #{result.invalid_usage_reason}"
|
49
|
+
puts
|
50
|
+
puts result.usage(File.basename($0))
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def instantiate_cli(klass, internals)
|
56
|
+
return klass unless klass.is_a?(Class)
|
57
|
+
|
58
|
+
state = internals.state
|
59
|
+
klass.new(state.flags, state.args, internals.result.named_args, internals)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Recursively look through sub_routes for the CLI to be used
|
63
|
+
def get_cli_object_and_met(cli, met, internals)
|
64
|
+
if DISALLOWED_SUBCOMMAND_NAMES.include?(met)
|
65
|
+
warn %(FATAL: Tabry does not support top-level subcommands named: #{DISALLOWED_SUBCOMMAND_NAMES.join(",")})
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
|
69
|
+
return [cli, "main"] if met.to_s == ""
|
70
|
+
|
71
|
+
sub_route_clis = cli.class.instance_variable_get(:@sub_route_clis)
|
72
|
+
sub_name, rest = met.split("__", 2)
|
73
|
+
|
74
|
+
if sub_route_clis&.dig(sub_name)
|
75
|
+
sub_route_clis[sub_name] = instantiate_cli(sub_route_clis[sub_name], internals)
|
76
|
+
get_cli_object_and_met(sub_route_clis[sub_name], rest, internals)
|
77
|
+
else
|
78
|
+
[cli, met]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def cli_send_met(cli, met)
|
83
|
+
if cli.respond_to?(met.to_sym)
|
84
|
+
run_hooks(cli, met, :@before_actions)
|
85
|
+
cli.send(met.to_sym)
|
86
|
+
run_hooks(cli, met, :@after_actions)
|
87
|
+
else
|
88
|
+
warn %(FATAL: CLI does not support command #{met})
|
89
|
+
exit 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def run_hooks(cli, met, instance_var)
|
94
|
+
met = met.to_s
|
95
|
+
cli.class.instance_variable_get(instance_var)&.each do |hook, opts|
|
96
|
+
next if Array(opts&.dig(:except))&.map(&:to_s)&.include?(met.to_s)
|
97
|
+
next if opts&.dig(:only) && !Array(opts[:only])&.map(&:to_s)&.include?(met.to_s)
|
98
|
+
|
99
|
+
hook.is_a?(Proc) ? cli.instance_eval(&hook) : cli.send(hook.to_sym)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tabry
|
4
|
+
module CLI
|
5
|
+
class FlagProxy
|
6
|
+
def initialize(hash)
|
7
|
+
@hash = hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
key = key.to_s
|
12
|
+
[
|
13
|
+
key,
|
14
|
+
key.gsub("_", "-")
|
15
|
+
].each do |hash_key|
|
16
|
+
val = @hash[hash_key]
|
17
|
+
return val if val
|
18
|
+
end
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def slice(*keys)
|
23
|
+
[keys].flatten.each_with_object({}) do |key, result_hash|
|
24
|
+
result_hash[key] = self[key]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(met, default = nil, *_args)
|
29
|
+
self[met] || default
|
30
|
+
end
|
31
|
+
|
32
|
+
def respond_to_missing?(*_args)
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
"FlagProxy: #{@hash.inspect}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"FlagProxy: #{@hash}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
require "yaml"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
# Simple YAML configuration
|
8
|
+
# Example:
|
9
|
+
# require 'tabry/cli/util/config'
|
10
|
+
# module MyCli
|
11
|
+
# Config = Tabry::CLI::Util::Config.new('~/.mycli.yml')
|
12
|
+
# end
|
13
|
+
|
14
|
+
class Hash
|
15
|
+
def to_openstruct
|
16
|
+
JSON.parse to_json, object_class: OpenStruct
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Array
|
21
|
+
def to_openstruct
|
22
|
+
JSON.parse to_json, object_class: OpenStruct
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Tabry
|
27
|
+
module CLI
|
28
|
+
module Util
|
29
|
+
class Config
|
30
|
+
def initialize(path)
|
31
|
+
@path = path.gsub(/^~/, Dir.home)
|
32
|
+
end
|
33
|
+
|
34
|
+
def config
|
35
|
+
@config ||= YAML.load_file(@path).to_openstruct
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(*args)
|
39
|
+
config.send(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def respond_to_missing?(*args)
|
43
|
+
super?(*args) || config.respond_to_missing?(*args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
# Utils that may be useful to CLIs
|
6
|
+
module Tabry
|
7
|
+
module CLI
|
8
|
+
module Util
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def make_cmdline(cmdline, *args, echo: false, echo_only: false)
|
12
|
+
args = Array(args).flatten
|
13
|
+
cmdline = cmdline % args.map { |a| Shellwords.escape(a) }
|
14
|
+
warn cmdline if echo || echo_only
|
15
|
+
return nil if echo_only
|
16
|
+
|
17
|
+
cmdline
|
18
|
+
end
|
19
|
+
|
20
|
+
def system(*cmdline, **opts)
|
21
|
+
cmdline = make_cmdline(*cmdline, **opts)
|
22
|
+
Kernel.system cmdline if cmdline
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO: would be nice to get separate STDERR and STDOUT
|
26
|
+
def backtick_or_die(*cmdline, **opts)
|
27
|
+
backtick_with_process_status(*cmdline, **opts).first
|
28
|
+
end
|
29
|
+
|
30
|
+
def backtick_with_process_status(*cmdline, valid_statuses: [0], **opts)
|
31
|
+
cmdline = make_cmdline(*cmdline, **opts)
|
32
|
+
return [nil, nil] unless cmdline
|
33
|
+
|
34
|
+
res = `#{cmdline}`
|
35
|
+
status = $?
|
36
|
+
if valid_statuses && !valid_statuses.include?(status.exitstatus)
|
37
|
+
warn "COMMAND FAILED with exit code #{status.exitstatus}: #{cmdline}"
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
[res, status]
|
41
|
+
end
|
42
|
+
|
43
|
+
def open_web_page(url)
|
44
|
+
command = RUBY_PLATFORM.include?("linux") ? "xdg-open" : "open"
|
45
|
+
unless system("(%s %s 2>&1) >/dev/null", command, url)
|
46
|
+
warn "WARNING: opening web page failed: #{make_cmdline("%s %s", command, url)}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "models/config"
|
4
|
+
|
5
|
+
module Tabry
|
6
|
+
class ConfigLoader
|
7
|
+
class ConfigNotFound < StandardError; end
|
8
|
+
|
9
|
+
EXTENSIONS = %w[json yml yaml].freeze
|
10
|
+
|
11
|
+
def self.load(**args)
|
12
|
+
new(**args).load
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
def initialize(name:)
|
18
|
+
@name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
def load
|
22
|
+
return load_from_file(name) if name =~ /\.json$/i || name =~ /\.ya?ml$/i
|
23
|
+
|
24
|
+
load_paths.each do |dir|
|
25
|
+
EXTENSIONS.each do |extension|
|
26
|
+
filename = "#{dir}/#{name}.#{extension}"
|
27
|
+
return load_from_file(filename) if File.exist?(filename)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
raise ConfigNotFound, "Could not find Tabry config #{name}.(#{EXTENSIONS.join(", ")}) in paths: #{load_paths.inspect}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def load_paths
|
37
|
+
@load_paths ||= [
|
38
|
+
*ENV["TABRY_IMPORTS_PATH"]&.split(":")&.reject(&:empty?),
|
39
|
+
Dir.home + "/.tabry/",
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_from_file(filename)
|
44
|
+
if filename =~ /\.json$/
|
45
|
+
require "json"
|
46
|
+
Tabry::Models::Config.new(raw: JSON.parse(File.read(filename)))
|
47
|
+
elsif filename =~ /\.ya?ml$/
|
48
|
+
require "yaml"
|
49
|
+
Tabry::Models::Config.new(raw: YAML.safe_load(File.read(filename)))
|
50
|
+
else
|
51
|
+
raise "unknown file type"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class String
|
4
|
+
def decolorize
|
5
|
+
gsub(/\e\[(\d+)(;\d+)*m/, "")
|
6
|
+
end
|
7
|
+
|
8
|
+
def black
|
9
|
+
"\e[30m#{self}\e[0m"
|
10
|
+
end
|
11
|
+
|
12
|
+
def red
|
13
|
+
"\e[31m#{self}\e[0m"
|
14
|
+
end
|
15
|
+
|
16
|
+
def green
|
17
|
+
"\e[32m#{self}\e[0m"
|
18
|
+
end
|
19
|
+
|
20
|
+
def brown
|
21
|
+
"\e[33m#{self}\e[0m"
|
22
|
+
end
|
23
|
+
|
24
|
+
def blue
|
25
|
+
"\e[34m#{self}\e[0m"
|
26
|
+
end
|
27
|
+
|
28
|
+
def magenta
|
29
|
+
"\e[35m#{self}\e[0m"
|
30
|
+
end
|
31
|
+
|
32
|
+
def cyan
|
33
|
+
"\e[36m#{self}\e[0m"
|
34
|
+
end
|
35
|
+
|
36
|
+
def gray
|
37
|
+
"\e[37m#{self}\e[0m"
|
38
|
+
end
|
39
|
+
|
40
|
+
def bg_black
|
41
|
+
"\e[40m#{self}\e[0m"
|
42
|
+
end
|
43
|
+
|
44
|
+
def bg_red
|
45
|
+
"\e[41m#{self}\e[0m"
|
46
|
+
end
|
47
|
+
|
48
|
+
def bg_green
|
49
|
+
"\e[42m#{self}\e[0m"
|
50
|
+
end
|
51
|
+
|
52
|
+
def bg_brown
|
53
|
+
"\e[43m#{self}\e[0m"
|
54
|
+
end
|
55
|
+
|
56
|
+
def bg_blue
|
57
|
+
"\e[44m#{self}\e[0m"
|
58
|
+
end
|
59
|
+
|
60
|
+
def bg_magenta
|
61
|
+
"\e[45m#{self}\e[0m"
|
62
|
+
end
|
63
|
+
|
64
|
+
def bg_cyan
|
65
|
+
"\e[46m#{self}\e[0m"
|
66
|
+
end
|
67
|
+
|
68
|
+
def bg_gray
|
69
|
+
"\e[47m#{self}\e[0m"
|
70
|
+
end
|
71
|
+
|
72
|
+
def bold
|
73
|
+
"\e[1m#{self}\e[22m"
|
74
|
+
end
|
75
|
+
|
76
|
+
def italic
|
77
|
+
"\e[3m#{self}\e[23m"
|
78
|
+
end
|
79
|
+
|
80
|
+
def underline
|
81
|
+
"\e[4m#{self}\e[24m"
|
82
|
+
end
|
83
|
+
|
84
|
+
def blink
|
85
|
+
"\e[5m#{self}\e[25m"
|
86
|
+
end
|
87
|
+
|
88
|
+
def reverse_color
|
89
|
+
"\e[7m#{self}\e[27m"
|
90
|
+
end
|
91
|
+
end
|