tabry 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|