eco-helpers 0.6.4 → 0.6.5
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/.yardopts +10 -0
- data/eco-helpers.gemspec +2 -2
- data/lib/eco/api/organization/tag_tree.rb +11 -11
- data/lib/eco/api/session/batch.rb +3 -8
- data/lib/eco/api/usecases/default_cases.rb +6 -2
- data/lib/eco/api/usecases/default_cases/create_case.rb +60 -0
- data/lib/eco/api/usecases/default_cases/switch_supervisor_case.rb +3 -4
- data/lib/eco/api/usecases/default_cases/update_case.rb +58 -0
- data/lib/eco/api/usecases/default_cases/upsert_case.rb +61 -0
- data/lib/eco/cli.rb +1 -2
- data/lib/eco/cli/api.rb +14 -0
- data/lib/eco/cli/root.rb +1 -0
- data/lib/eco/common.rb +1 -0
- data/lib/eco/common/base_cli.rb +6 -99
- data/lib/eco/common/base_cli_backup.rb +119 -0
- data/lib/eco/common/meta_thor.rb +111 -0
- data/lib/eco/common/meta_thor/command_group.rb +36 -0
- data/lib/eco/common/meta_thor/command_unit.rb +48 -0
- data/lib/eco/common/meta_thor/input_backup.rb +111 -0
- data/lib/eco/common/meta_thor/input_multi_backup.rb +139 -0
- data/lib/eco/common/meta_thor/pipe.rb +85 -0
- data/lib/eco/common/meta_thor/thor.rb +1 -0
- data/lib/eco/common/meta_thor/thor/command.rb +36 -0
- data/lib/eco/common/meta_thor/value.rb +54 -0
- data/lib/eco/version.rb +1 -1
- metadata +16 -4
- data/lib/eco/api/usecases/default_cases/upsert_account_case.rb +0 -35
- data/lib/eco/cli/input.rb +0 -109
- data/lib/eco/cli/input_multi.rb +0 -137
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Eco
|
4
|
+
module Common
|
5
|
+
class BaseCLI < MethaThor
|
6
|
+
PIPE = "!"
|
7
|
+
CUSTOM_HELP_MAPPINGS = ["?", "help"]
|
8
|
+
ALL_HELP_MAPPINGS = Thor::HELP_MAPPINGS + CUSTOM_HELP_MAPPINGS
|
9
|
+
|
10
|
+
class_option :simulate, type: :boolean, aliases: :s, desc: "do not launch updates or rewrite files"
|
11
|
+
class_option :verbosity, type: :numeric, default: 0, desc: "defines the level of verbosity to show feedback"
|
12
|
+
|
13
|
+
class_option :input, type: :hash, default: {json: nil}, required: false, desc: "--input=json:STRING"
|
14
|
+
class_option :pipe, type: :boolean, default: false, required: false, desc: "--pipe subcommand"
|
15
|
+
|
16
|
+
def self.start(given_args = ARGV, config = {})
|
17
|
+
#given_args = splat_namespaces(given_args)
|
18
|
+
given_args = BaseCLI.parse_help(given_args)
|
19
|
+
super(given_args, config)
|
20
|
+
end
|
21
|
+
|
22
|
+
# incompatible with options / arguments of Hash type
|
23
|
+
# see: parse_hash here: https://github.com/erikhuda/thor/blob/master/lib/thor/parser/arguments.rb
|
24
|
+
def self.splat_namespaces(args)
|
25
|
+
args.reduce([]) do |done, arg|
|
26
|
+
done.concat(arg.split(":"))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_help(args)
|
31
|
+
# Help enhancement. Adapted from: https://stackoverflow.com/a/49044225/4352306
|
32
|
+
last = args.last
|
33
|
+
if args.length > 1 && help?(last)
|
34
|
+
# switch last and second last position
|
35
|
+
last = "help" if custom_help?(last)
|
36
|
+
args.insert(args.length - 2, last).pop
|
37
|
+
puts "> #{args.join(" ")}"
|
38
|
+
end
|
39
|
+
args
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.help?(value)
|
43
|
+
case value
|
44
|
+
when String
|
45
|
+
ALL_HELP_MAPPINGS.include?(value)
|
46
|
+
when Symbol
|
47
|
+
help?(value.to_s)
|
48
|
+
when Array
|
49
|
+
value.any? { |v| help?(v) }
|
50
|
+
when Hash
|
51
|
+
value.keys.any { |k| help?(v) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.custom_help?(value)
|
56
|
+
CUSTOM_HELP_MAPPINGS.include?(value)
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
no_commands do
|
62
|
+
def input?(options)
|
63
|
+
if options
|
64
|
+
options.key?("input") || options.key?(:input) ||
|
65
|
+
options.key?("json") || options.key?(:json)
|
66
|
+
end
|
67
|
+
|
68
|
+
def input_object(input)
|
69
|
+
Eco::CLI::Input.new(input)
|
70
|
+
end
|
71
|
+
|
72
|
+
def input(value)
|
73
|
+
case
|
74
|
+
when value.is_a?(Eco::CLI::Input)
|
75
|
+
value.value
|
76
|
+
when value.is_a?(Eco::CLI::MultiInput)
|
77
|
+
value.to_h
|
78
|
+
when value.is_a?(String)
|
79
|
+
input(JSON.parse(value))
|
80
|
+
when value&.key?("input")
|
81
|
+
input(value["input"])
|
82
|
+
when value&.key?(:input)
|
83
|
+
input(value[:input])
|
84
|
+
when value&.key?("json")
|
85
|
+
JSON.parse(value["json"])
|
86
|
+
when value&.key?(:json)
|
87
|
+
JSON.parse(value[:json])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def pipe(input, args)
|
92
|
+
command, args, piped = parse_pipe(args)
|
93
|
+
invoke command, args, {input: input_object(input)} if piped
|
94
|
+
piped
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse_pipe(args)
|
98
|
+
args.shift if piped = (args.first == PIPE)
|
99
|
+
subcommand = ""
|
100
|
+
if piped
|
101
|
+
raise "Error: bad usage of pipe ('#{PIPE}'). Expected: '#{PIPE} command' " unless subcommand = args.slice!(0)
|
102
|
+
if help?(subcommand)
|
103
|
+
# Idea adapted from: https://stackoverflow.com/a/46167300/4352306
|
104
|
+
self.class.command_help(Thor::Base.shell.new, args.first)
|
105
|
+
exit
|
106
|
+
end
|
107
|
+
end
|
108
|
+
[subcommand, args, piped]
|
109
|
+
end
|
110
|
+
|
111
|
+
def help?(value)
|
112
|
+
BaseCLI.help?(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Eco
|
4
|
+
module Common
|
5
|
+
class MetaThor < Thor
|
6
|
+
INPUT_OPTION = "input"
|
7
|
+
INPUT_SYM = :input
|
8
|
+
INPUT_KEY = "json"
|
9
|
+
CUSTOM_HELP_MAPPINGS = ["?", "help"]
|
10
|
+
ALL_HELP_MAPPINGS = ::Thor::HELP_MAPPINGS + CUSTOM_HELP_MAPPINGS
|
11
|
+
|
12
|
+
class_option :input, type: :hash, default: {json: nil}, required: false, desc: "--input=json:STRING"
|
13
|
+
class_option :pipe, type: :boolean, default: false, required: false, desc: "--pipe subcommand"
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def start(given_args = ARGV, config = {})
|
17
|
+
config[:shell] ||= Thor::Base.shell.new
|
18
|
+
CommandGroup.new(given_args).map do |unit|
|
19
|
+
args = unit.args {|args| parse_custom_help(args)}
|
20
|
+
input = unit.input&.as_option
|
21
|
+
result = dispatch(nil, args.dup, input, config)
|
22
|
+
exit if help?(args)
|
23
|
+
unit.output = Value.new(result)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @note: it allows to use `help` at the end (alias: `?`)
|
28
|
+
# If there is custom help at the end of the current command,
|
29
|
+
# normalize custom help to native `help` and switch last
|
30
|
+
# and second last arguments
|
31
|
+
def parse_custom_help(args)
|
32
|
+
return args unless custom_help?(args&.last)
|
33
|
+
# move from custom to native help
|
34
|
+
if args.length > 1
|
35
|
+
# switch args last and second last position
|
36
|
+
args.insert(args.length - 2, "help").pop
|
37
|
+
else
|
38
|
+
args[0] = "help"
|
39
|
+
end
|
40
|
+
args
|
41
|
+
end
|
42
|
+
|
43
|
+
def custom_help?(value)
|
44
|
+
CUSTOM_HELP_MAPPINGS.include?(value)
|
45
|
+
end
|
46
|
+
|
47
|
+
def help?(value)
|
48
|
+
case value
|
49
|
+
when String
|
50
|
+
ALL_HELP_MAPPINGS.include?(value)
|
51
|
+
when Symbol
|
52
|
+
help?(value.to_s)
|
53
|
+
when Array
|
54
|
+
value.any? { |v| help?(v) }
|
55
|
+
when Hash
|
56
|
+
value.keys.any? { |k| help?(v) }
|
57
|
+
else
|
58
|
+
"what? #{value}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
no_commands do
|
67
|
+
def input?(options)
|
68
|
+
options &&
|
69
|
+
(options.key?(INPUT_OPTION) || options.key?(INPUT_KEY) ||
|
70
|
+
options.key?(Value::KEY_OPTION) )
|
71
|
+
end
|
72
|
+
|
73
|
+
def input(value)
|
74
|
+
|
75
|
+
case
|
76
|
+
when value.is_a?(Value)
|
77
|
+
value.value
|
78
|
+
when value.is_a?(String)
|
79
|
+
input(JSON.parse(value))
|
80
|
+
when value.is_a?(Hash)
|
81
|
+
case
|
82
|
+
when value.key?(Value::KEY_OPTION)
|
83
|
+
Value.to_value(value)
|
84
|
+
when value.key?(INPUT_OPTION)
|
85
|
+
input(value[INPUT_OPTION])
|
86
|
+
when value.key?(INPUT_SYM)
|
87
|
+
input(value[INPUT_SYM])
|
88
|
+
when value.key?(INPUT_KEY)
|
89
|
+
val = value[INPUT_KEY]
|
90
|
+
JSON.parse(val) unless val.is_a?(Hash)
|
91
|
+
else
|
92
|
+
value
|
93
|
+
end
|
94
|
+
else
|
95
|
+
value
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
require_relative 'meta_thor/pipe'
|
106
|
+
require_relative 'meta_thor/command_unit'
|
107
|
+
require_relative 'meta_thor/command_group'
|
108
|
+
require_relative 'meta_thor/value'
|
109
|
+
require_relative 'meta_thor/thor'
|
110
|
+
#require_relative 'metha_thor/input'
|
111
|
+
#require_relative 'metha_thor/input_multi'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Eco
|
2
|
+
module Common
|
3
|
+
class MetaThor
|
4
|
+
class CommandGroup
|
5
|
+
|
6
|
+
attr_reader :units
|
7
|
+
|
8
|
+
def initialize(args)
|
9
|
+
args = (args && [args].flatten) || []
|
10
|
+
#pp "CommandGroup split args: #{Pipe.split(args)}"
|
11
|
+
@units = Pipe.split(args).each_with_index.map do |arguments, i|
|
12
|
+
CommandUnit.new(args: arguments, group: self, index: i)
|
13
|
+
end
|
14
|
+
@units = units
|
15
|
+
end
|
16
|
+
|
17
|
+
def print
|
18
|
+
@units.map do |command|
|
19
|
+
pp command.args
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](value)
|
24
|
+
@units[value]
|
25
|
+
end
|
26
|
+
|
27
|
+
def map
|
28
|
+
@units.map do |unit|
|
29
|
+
yield(unit)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Eco
|
2
|
+
module Common
|
3
|
+
class MetaThor
|
4
|
+
class CommandUnit
|
5
|
+
class << self
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :group, :index
|
10
|
+
attr_reader :pipe
|
11
|
+
attr_reader :args, :source_args
|
12
|
+
attr_accessor :output
|
13
|
+
|
14
|
+
def initialize(args:, group:, index:)
|
15
|
+
@source_args = args.dup || []
|
16
|
+
@args = @source_args
|
17
|
+
@args = @args.slice(1..-1) if Pipe.piped?(@source_args)
|
18
|
+
@pipe = Pipe.new(command: self) if Pipe.piped?(@source_args)
|
19
|
+
|
20
|
+
@group = group
|
21
|
+
@index = index
|
22
|
+
end
|
23
|
+
|
24
|
+
def piped?
|
25
|
+
!!@pipe
|
26
|
+
end
|
27
|
+
|
28
|
+
def input
|
29
|
+
pipe&.input
|
30
|
+
end
|
31
|
+
|
32
|
+
def args
|
33
|
+
arguments = @args.dup
|
34
|
+
arguments = yield(arguments) if block_given?
|
35
|
+
arguments
|
36
|
+
end
|
37
|
+
|
38
|
+
def piped_args
|
39
|
+
args.tap do |arguments|
|
40
|
+
arguments = yield(arguments) if block_given?
|
41
|
+
arguments.push(pipe.input.as_input_option) if piped?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Eco
|
2
|
+
module Common
|
3
|
+
class MethaThor
|
4
|
+
class Input
|
5
|
+
MODES = [:both, :json, :value]
|
6
|
+
|
7
|
+
attr_reader :mode
|
8
|
+
|
9
|
+
def initialize(value, mode = :both)
|
10
|
+
@mode = mode
|
11
|
+
@mode = :both unless MODES.include?(mode)
|
12
|
+
|
13
|
+
@doc = {}
|
14
|
+
@doc['value'] = {} if self.value?
|
15
|
+
@doc['json'] = {} if self.json?
|
16
|
+
|
17
|
+
self.value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
@doc
|
22
|
+
end
|
23
|
+
|
24
|
+
def json?
|
25
|
+
@mode == :json || @mode = :both
|
26
|
+
end
|
27
|
+
|
28
|
+
def value?
|
29
|
+
@mode == :value || @mode = :both
|
30
|
+
end
|
31
|
+
|
32
|
+
def json
|
33
|
+
@doc['json'] if self.json?
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse
|
37
|
+
JSON.parse(self.json) if self.json?
|
38
|
+
end
|
39
|
+
|
40
|
+
def json=(value)
|
41
|
+
@doc['value'] = value if self.value?
|
42
|
+
@doc['json'] = to_json(value) if self.json?
|
43
|
+
end
|
44
|
+
|
45
|
+
def value
|
46
|
+
return @doc['value'] if self.value?
|
47
|
+
self.parse
|
48
|
+
end
|
49
|
+
|
50
|
+
def value=(value)
|
51
|
+
@doc['value'] = value if self.value?
|
52
|
+
@doc['json'] = to_json(value) if self.json?
|
53
|
+
end
|
54
|
+
|
55
|
+
def present?(key)
|
56
|
+
self[key]&.present?
|
57
|
+
end
|
58
|
+
|
59
|
+
def empty?
|
60
|
+
return @doc['value'].keys.length == 0 if self.value?
|
61
|
+
@doc['json'].keys.length == 0
|
62
|
+
end
|
63
|
+
|
64
|
+
# merging implies that @mode is equalized to the lesser
|
65
|
+
# => :both.merge(:json) == :json ; :value.merge(:both) == :value mode
|
66
|
+
# => :json.merge(:value) will raise an exception
|
67
|
+
def merge(value)
|
68
|
+
merge_hash(hash(value))
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def to_json(value)
|
75
|
+
if value.respond_to?(:as_json)
|
76
|
+
input.as_json
|
77
|
+
elsif value.respond_to?(:to_json)
|
78
|
+
value.to_json
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def doc_mode(doc)
|
83
|
+
case doc
|
84
|
+
when String
|
85
|
+
doc = JSON.parse(doc)
|
86
|
+
return nil unless doc.is_a?(Hash)
|
87
|
+
doc_mode(doc)
|
88
|
+
when CliInput
|
89
|
+
doc.mode
|
90
|
+
when Hash
|
91
|
+
mod_json = doc.key?('json')
|
92
|
+
mod_value = doc.key?('value')
|
93
|
+
mod_json ? (mod_value ? :both : :json) : (mod_value ? :value : nil)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def hash(value)
|
98
|
+
case value
|
99
|
+
when String
|
100
|
+
JSON.parse(value)
|
101
|
+
when CliInput
|
102
|
+
value.to_h
|
103
|
+
when Hash
|
104
|
+
value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Eco
|
2
|
+
module Common
|
3
|
+
class MethaThor
|
4
|
+
class MultiInput
|
5
|
+
MODES = [:both, :json, :value]
|
6
|
+
|
7
|
+
attr_reader :mode
|
8
|
+
|
9
|
+
def initialize(doc = {}, mode = :both)
|
10
|
+
@mode = doc_mode(doc) || mode
|
11
|
+
@mode = :both unless MODES.include?(@mode)
|
12
|
+
|
13
|
+
@doc = {}
|
14
|
+
@doc['value'] = {} if self.value?
|
15
|
+
@doc['json'] = {} if self.json?
|
16
|
+
|
17
|
+
self.merge(doc) unless doc.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
@doc
|
22
|
+
end
|
23
|
+
|
24
|
+
def json?
|
25
|
+
@mode == :json || @mode = :both
|
26
|
+
end
|
27
|
+
|
28
|
+
def value?
|
29
|
+
@mode == :value || @mode = :both
|
30
|
+
end
|
31
|
+
|
32
|
+
def json(key)
|
33
|
+
@doc.dig('json', key)
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
return @doc.dig('value', key) if self.value?
|
38
|
+
JSON.parse(@doc.dig('json', key))
|
39
|
+
end
|
40
|
+
|
41
|
+
def []=(key, value)
|
42
|
+
@doc['value'][key] = value if self.value?
|
43
|
+
@doc['json'][key] = to_json(value) if self.json?
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(key)
|
47
|
+
if self.key?(key)
|
48
|
+
value = @doc['value'].delete(key) if self.value?
|
49
|
+
value2 = JSON.parse(@doc['json'].delete(key)) if self.json?
|
50
|
+
return value if self.value?
|
51
|
+
value2
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def key?(key)
|
56
|
+
return @doc['value'].key?(key) if self.value?
|
57
|
+
@doc['json'].key?(key)
|
58
|
+
end
|
59
|
+
|
60
|
+
def present?(key)
|
61
|
+
self[key]&.present?
|
62
|
+
end
|
63
|
+
|
64
|
+
def empty?
|
65
|
+
return @doc['value'].keys.length == 0 if self.value?
|
66
|
+
@doc['json'].keys.length == 0
|
67
|
+
end
|
68
|
+
|
69
|
+
# merging implies that @mode is equalized to the lesser
|
70
|
+
# => :both.merge(:json) == :json ; :value.merge(:both) == :value mode
|
71
|
+
# => :json.merge(:value) will raise an exception
|
72
|
+
def merge(value)
|
73
|
+
merge_hash(hash(value))
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def to_json(value)
|
80
|
+
if value.respond_to?(:as_json)
|
81
|
+
input.as_json
|
82
|
+
elsif value.respond_to?(:to_json)
|
83
|
+
value.to_json
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def doc_mode(doc)
|
88
|
+
case doc
|
89
|
+
when String
|
90
|
+
doc = JSON.parse(doc)
|
91
|
+
return nil unless doc.is_a?(Hash)
|
92
|
+
doc_mode(doc)
|
93
|
+
when CliInput
|
94
|
+
doc.mode
|
95
|
+
when Hash
|
96
|
+
mod_json = doc.key?('json')
|
97
|
+
mod_value = doc.key?('value')
|
98
|
+
mod_json ? (mod_value ? :both : :json) : (mod_value ? :value : nil)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def hash(value)
|
103
|
+
case value
|
104
|
+
when String
|
105
|
+
JSON.parse(value)
|
106
|
+
when CliInput
|
107
|
+
value.to_h
|
108
|
+
when Hash
|
109
|
+
value
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def merge_hash(value)
|
114
|
+
err_input = "Error: merge requires a correct Hashable value or a #{self.class.name} instance."
|
115
|
+
err_incompatible = "Error: can't merge value mode with json mode."
|
116
|
+
raise err_input unless value.is_a?(Hash)
|
117
|
+
return unless !value.empty?
|
118
|
+
|
119
|
+
raise err_input if ! (mode = doc_mode(value))
|
120
|
+
raise err_incompatible if ! (new_mode = resolve_mode(mode))
|
121
|
+
@mode = new_mode
|
122
|
+
|
123
|
+
@doc.delete('json') unless self.json?
|
124
|
+
@doc.delete('value') unless self.value?
|
125
|
+
|
126
|
+
@doc['json'].merge(value['json']) if self.json?
|
127
|
+
@doc['value'].merge(value['value']) if self.value?
|
128
|
+
end
|
129
|
+
|
130
|
+
def resolve_mode(mode)
|
131
|
+
return mode if @mode == :both
|
132
|
+
return @mode if mode == :both
|
133
|
+
return mode if @mode == mode
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|