eco-helpers 0.6.4 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|