haveapi-client 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG +17 -0
- data/README.md +14 -2
- data/haveapi-client.gemspec +5 -5
- data/lib/haveapi/cli.rb +5 -0
- data/lib/haveapi/cli/cli.rb +528 -277
- data/lib/haveapi/cli/command.rb +52 -0
- data/lib/haveapi/cli/output_formatter.rb +221 -0
- data/lib/haveapi/client.rb +5 -0
- data/lib/haveapi/client/action.rb +102 -98
- data/lib/haveapi/client/authentication/token.rb +1 -0
- data/lib/haveapi/client/client.rb +21 -3
- data/lib/haveapi/client/communicator.rb +161 -129
- data/lib/haveapi/client/exceptions.rb +23 -10
- data/lib/haveapi/client/parameters/resource.rb +29 -0
- data/lib/haveapi/client/parameters/typed.rb +72 -0
- data/lib/haveapi/client/params.rb +72 -0
- data/lib/haveapi/client/resource_instance.rb +2 -0
- data/lib/haveapi/client/validator.rb +55 -0
- data/lib/haveapi/client/validators/acceptance.rb +9 -0
- data/lib/haveapi/client/validators/confirmation.rb +18 -0
- data/lib/haveapi/client/validators/custom.rb +9 -0
- data/lib/haveapi/client/validators/exclusion.rb +9 -0
- data/lib/haveapi/client/validators/format.rb +16 -0
- data/lib/haveapi/client/validators/inclusion.rb +14 -0
- data/lib/haveapi/client/validators/length.rb +14 -0
- data/lib/haveapi/client/validators/numericality.rb +24 -0
- data/lib/haveapi/client/validators/presence.rb +11 -0
- data/lib/haveapi/client/version.rb +3 -2
- metadata +28 -25
@@ -0,0 +1,52 @@
|
|
1
|
+
module HaveAPI::CLI
|
2
|
+
class Command
|
3
|
+
class << self
|
4
|
+
attr_reader :resource, :action
|
5
|
+
|
6
|
+
def cmd(resource, action = nil)
|
7
|
+
@resource = resource.is_a?(::Array) ? resource : [resource]
|
8
|
+
@resource.map! { |v| v.to_s }
|
9
|
+
@action = action && action.to_s
|
10
|
+
|
11
|
+
Cli.register_command(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def args(v = nil)
|
15
|
+
if v
|
16
|
+
@args = v
|
17
|
+
|
18
|
+
else
|
19
|
+
@args
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def desc(v = nil)
|
24
|
+
if v
|
25
|
+
@desc = v
|
26
|
+
|
27
|
+
else
|
28
|
+
@desc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle?(resource, action)
|
33
|
+
resource == @resource && action == @action
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :global_opts
|
38
|
+
|
39
|
+
def initialize(opts, client)
|
40
|
+
@global_opts = opts
|
41
|
+
@api = client
|
42
|
+
end
|
43
|
+
|
44
|
+
def options(opts)
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def exec(args)
|
49
|
+
raise NotImplementedError
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module HaveAPI::CLI
|
2
|
+
class OutputFormatter
|
3
|
+
def self.format(*args)
|
4
|
+
f = new(*args)
|
5
|
+
f.format
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.print(*args)
|
9
|
+
f = new(*args)
|
10
|
+
f.print
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(objects, cols = nil, header: true, sort: nil, layout: nil, empty: '-')
|
14
|
+
@objects = objects
|
15
|
+
@header = header
|
16
|
+
@sort = sort
|
17
|
+
@layout = layout
|
18
|
+
@empty = empty
|
19
|
+
|
20
|
+
if @layout.nil?
|
21
|
+
if many?
|
22
|
+
@layout = :columns
|
23
|
+
|
24
|
+
else
|
25
|
+
@layout = :rows
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if cols
|
30
|
+
@cols = parse_cols(cols)
|
31
|
+
|
32
|
+
else
|
33
|
+
if @objects.is_a?(::Array) # A list of items
|
34
|
+
@cols ||= parse_cols(@objects.first.keys)
|
35
|
+
|
36
|
+
elsif @objects.is_a?(::Hash) # Single item
|
37
|
+
@cols ||= parse_cols(@objects.keys)
|
38
|
+
|
39
|
+
else
|
40
|
+
fail "unsupported type #{@objects.class}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def format
|
46
|
+
@out = ''
|
47
|
+
generate
|
48
|
+
@out
|
49
|
+
end
|
50
|
+
|
51
|
+
def print
|
52
|
+
@out = nil
|
53
|
+
generate
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
def parse_cols(cols)
|
58
|
+
ret = []
|
59
|
+
|
60
|
+
cols.each do |c|
|
61
|
+
base = {
|
62
|
+
align: 'left'
|
63
|
+
}
|
64
|
+
|
65
|
+
if c.is_a?(::String) || c.is_a?(::Symbol)
|
66
|
+
base.update({
|
67
|
+
name: c,
|
68
|
+
label: c.to_s.upcase,
|
69
|
+
})
|
70
|
+
ret << base
|
71
|
+
|
72
|
+
elsif c.is_a?(::Hash)
|
73
|
+
base.update(c)
|
74
|
+
ret << base
|
75
|
+
|
76
|
+
else
|
77
|
+
fail "unsupported column type #{c.class}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
ret
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate
|
85
|
+
return if @cols.empty?
|
86
|
+
prepare
|
87
|
+
|
88
|
+
case @layout
|
89
|
+
when :columns
|
90
|
+
columns
|
91
|
+
|
92
|
+
when :rows
|
93
|
+
rows
|
94
|
+
|
95
|
+
else
|
96
|
+
fail "unsupported layout '#{@layout}'"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Each object is printed on one line, it's parameters aligned into columns.
|
101
|
+
def columns
|
102
|
+
i = 0
|
103
|
+
|
104
|
+
formatters = @cols.map do |c|
|
105
|
+
ret = case c[:align].to_sym
|
106
|
+
when :right
|
107
|
+
"%#{col_width(i, c)}s"
|
108
|
+
|
109
|
+
else
|
110
|
+
"%-#{col_width(i, c)}s"
|
111
|
+
end
|
112
|
+
|
113
|
+
i += 1
|
114
|
+
ret
|
115
|
+
end.join(' ')
|
116
|
+
|
117
|
+
line sprintf(formatters, * @cols.map { |c| c[:label] }) if @header
|
118
|
+
|
119
|
+
@str_objects.each do |o|
|
120
|
+
line sprintf(formatters, *o)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Each object is printed on multiple lines, one parameter per line.
|
125
|
+
def rows
|
126
|
+
w = heading_width
|
127
|
+
|
128
|
+
@str_objects.each do |o|
|
129
|
+
@cols.each_index do |i|
|
130
|
+
c = @cols[i]
|
131
|
+
|
132
|
+
if o[i].is_a?(::String) && o[i].index("\n")
|
133
|
+
lines = o[i].split("\n")
|
134
|
+
v = ([lines.first] + lines[1..-1].map { |l| (' ' * (w+3)) + l }).join("\n")
|
135
|
+
|
136
|
+
else
|
137
|
+
v = o[i]
|
138
|
+
end
|
139
|
+
|
140
|
+
line sprintf("%#{w}s: %s", c[:label], v)
|
141
|
+
end
|
142
|
+
|
143
|
+
line
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def line(str = '')
|
148
|
+
if @out
|
149
|
+
@out += str + "\n"
|
150
|
+
|
151
|
+
else
|
152
|
+
puts str
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def prepare
|
157
|
+
@str_objects = []
|
158
|
+
|
159
|
+
each_object do |o|
|
160
|
+
arr = []
|
161
|
+
|
162
|
+
@cols.each do |c|
|
163
|
+
v = o[ c[:name] ]
|
164
|
+
str = (c[:display] ? c[:display].call(v) : v)
|
165
|
+
str = @empty if !str || (str.is_a?(::String) && str.empty?)
|
166
|
+
|
167
|
+
arr << str
|
168
|
+
end
|
169
|
+
|
170
|
+
@str_objects << arr
|
171
|
+
end
|
172
|
+
|
173
|
+
if @sort
|
174
|
+
col_i = @cols.index { |c| c[:name] == @sort }
|
175
|
+
fail "unknown column '#{@sort}'" unless col_i
|
176
|
+
|
177
|
+
@str_objects.sort! do |a, b|
|
178
|
+
a[col_i] <=> b[col_i]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
@str_objects
|
183
|
+
end
|
184
|
+
|
185
|
+
def col_width(i, c)
|
186
|
+
w = c[:label].to_s.length
|
187
|
+
|
188
|
+
@str_objects.each do |o|
|
189
|
+
len = o[i].to_s.length
|
190
|
+
w = len if len > w
|
191
|
+
end
|
192
|
+
|
193
|
+
w + 1
|
194
|
+
end
|
195
|
+
|
196
|
+
def heading_width
|
197
|
+
w = @cols.first[:label].to_s.length
|
198
|
+
|
199
|
+
@cols.each do |c|
|
200
|
+
len = c[:label].to_s.length
|
201
|
+
|
202
|
+
w = len if len > w
|
203
|
+
end
|
204
|
+
|
205
|
+
w + 1
|
206
|
+
end
|
207
|
+
|
208
|
+
def each_object
|
209
|
+
if @objects.is_a?(::Array)
|
210
|
+
@objects.each { |v| yield(v) }
|
211
|
+
|
212
|
+
else
|
213
|
+
yield(@objects)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def many?
|
218
|
+
@objects.is_a?(::Array) && @objects.size > 1
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/lib/haveapi/client.rb
CHANGED
@@ -1,131 +1,135 @@
|
|
1
|
-
module HaveAPI
|
2
|
-
|
3
|
-
|
4
|
-
attr_accessor :resource_path
|
1
|
+
module HaveAPI::Client
|
2
|
+
class Action
|
3
|
+
attr_accessor :resource_path
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
def initialize(api, name, spec, args)
|
6
|
+
@api = api
|
7
|
+
@name = name
|
8
|
+
@spec = spec
|
10
9
|
|
11
|
-
|
12
|
-
|
10
|
+
apply_args(args)
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
def execute(data, *_)
|
14
|
+
params = Params.new(self, data)
|
15
|
+
|
16
|
+
unless params.valid?
|
17
|
+
raise ValidationError.new(self, params.errors)
|
19
18
|
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
ret = @api.call(self, params.to_api)
|
21
|
+
@prepared_url = nil
|
22
|
+
@prepared_help = nil
|
23
|
+
ret
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def name
|
27
|
+
@name
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
else
|
33
|
-
@spec[:aliases]
|
34
|
-
end
|
35
|
-
end
|
30
|
+
def auth?
|
31
|
+
@spec[:auth]
|
32
|
+
end
|
36
33
|
|
37
|
-
|
38
|
-
|
34
|
+
def aliases(include_name = false)
|
35
|
+
if include_name
|
36
|
+
[@name] + @spec[:aliases]
|
37
|
+
else
|
38
|
+
@spec[:aliases]
|
39
39
|
end
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
def description
|
43
|
+
@spec[:description]
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
def input
|
47
|
+
@spec[:input]
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
def output
|
51
|
+
@spec[:output]
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
def input_layout
|
55
|
+
@spec[:input][:layout].to_sym
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
def output_layout
|
59
|
+
@spec[:output][:layout].to_sym
|
60
|
+
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
def structure
|
63
|
+
@spec[:output][:format]
|
64
|
+
end
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
def namespace(src)
|
67
|
+
@spec[src][:namespace]
|
68
|
+
end
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
70
|
+
def examples
|
71
|
+
@spec[:examples]
|
72
|
+
end
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
def input_params
|
75
|
+
@spec[:input][:parameters]
|
76
|
+
end
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
def params
|
79
|
+
@spec[:output][:parameters]
|
80
|
+
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
def param_description(dir, name)
|
83
|
+
@spec[dir][:parameters][name]
|
84
|
+
end
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
def url
|
87
|
+
@spec[:url]
|
88
|
+
end
|
88
89
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
end
|
90
|
+
def help
|
91
|
+
@spec[:help]
|
92
|
+
end
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
# Url with resolved parameters.
|
95
|
+
def prepared_url
|
96
|
+
@prepared_url || @spec[:url]
|
97
|
+
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
def prepared_help
|
100
|
+
@prepared_help || @spec[:help]
|
101
|
+
end
|
101
102
|
|
102
|
-
|
103
|
-
|
104
|
-
|
103
|
+
def http_method
|
104
|
+
@spec[:method]
|
105
|
+
end
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
107
|
+
def unresolved_args?
|
108
|
+
prepared_url =~ /:[a-zA-Z\-_]+/
|
109
|
+
end
|
109
110
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
111
|
+
def provide_args(*args)
|
112
|
+
apply_args(args)
|
113
|
+
end
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
def provide_url(url, help)
|
116
|
+
@prepared_url = url
|
117
|
+
@prepared_help = help
|
118
|
+
end
|
118
119
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
120
|
+
def update_description(spec)
|
121
|
+
@spec = spec
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def apply_args(args)
|
126
|
+
@prepared_url ||= @spec[:url].dup
|
127
|
+
@prepared_help ||= @spec[:help].dup
|
123
128
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
+
args.each do |arg|
|
130
|
+
@prepared_url.sub!(/:[a-zA-Z\-_]+/, arg.to_s)
|
131
|
+
@prepared_help.sub!(/:[a-zA-Z\-_]+/, arg.to_s)
|
132
|
+
end
|
129
133
|
end
|
130
134
|
end
|
131
135
|
end
|