mvcli 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mvcli/actions.rb +2 -0
- data/lib/mvcli/app.rb +3 -2
- data/lib/mvcli/argv.rb +48 -0
- data/lib/mvcli/decoding.rb +25 -0
- data/lib/mvcli/form/input.rb +72 -0
- data/lib/mvcli/form.rb +87 -0
- data/lib/mvcli/router.rb +4 -6
- data/lib/mvcli/validatable.rb +188 -0
- data/lib/mvcli/version.rb +1 -1
- data/lib/mvcli.rb +0 -1
- data/spec/mvcli/argv_spec.rb +27 -0
- data/spec/mvcli/form/input_spec.rb +95 -0
- data/spec/mvcli/form_spec.rb +116 -0
- data/spec/mvcli/router_spec.rb +6 -0
- data/spec/mvcli/validatable_spec.rb +24 -0
- metadata +16 -4
- data/example/controllers/loadbalancers_controller.rb +0 -10
- data/example/routes.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39bb6d037a235e80b610d16a1cff026dd0945134
|
4
|
+
data.tar.gz: 42a1e909308dea92677bf8e76abb6dcee1d8358f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2812db9dc93f3a3ec99d6cfa5b2af883e6ed04af8aea8538404b28b5969234c14c3b71615996011dbd348e7e4343de0ec923e48078487e3d3142a9da6b6b5a8f
|
7
|
+
data.tar.gz: e2913626bac2de10934365c49a80924365885aac67c7bf23f76764a1ed8ca53fd08792b38dd04dae92aa5c2619385b89b44309552d46216115203bf148b962ab
|
data/lib/mvcli/actions.rb
CHANGED
data/lib/mvcli/app.rb
CHANGED
@@ -12,8 +12,9 @@ module MVCLI
|
|
12
12
|
def initialize
|
13
13
|
@router = Router.new Actions.new root
|
14
14
|
@router.instance_eval route_file.read, route_file.to_s, 1
|
15
|
-
|
16
|
-
|
15
|
+
[:providers, :controllers, :forms, :models].each do |path|
|
16
|
+
ActiveSupport::Dependencies.autoload_paths << root.join('app', path.to_s)
|
17
|
+
end
|
17
18
|
@middleware = Middleware.new
|
18
19
|
@middleware << Provisioning::Middleware.new
|
19
20
|
@middleware << @router
|
data/lib/mvcli/argv.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "map"
|
2
|
+
|
3
|
+
class MVCLI::Argv
|
4
|
+
attr_reader :arguments, :options
|
5
|
+
|
6
|
+
def initialize(argv, switches = [])
|
7
|
+
@switches = switches.map(&:to_s)
|
8
|
+
@arguments, @options = scan argv
|
9
|
+
end
|
10
|
+
|
11
|
+
def scan(argv, arguments = [], options = Map.new)
|
12
|
+
current, *rest = argv
|
13
|
+
case current
|
14
|
+
when nil
|
15
|
+
[arguments, options]
|
16
|
+
when /^--(\w[[:graph:]]+)=([[:graph:]]+)$/
|
17
|
+
scan rest, arguments, merge(options, $1, $2)
|
18
|
+
when /^--no-(\w[[:graph:]]+)$/
|
19
|
+
scan rest, arguments, merge(options, $1, false)
|
20
|
+
when /^--(\w[[:graph:]]+)$/, /^-(\w)$/
|
21
|
+
key = underscore $1
|
22
|
+
if switch? key
|
23
|
+
scan rest, arguments, merge(options, key, true)
|
24
|
+
elsif rest.first =~ /^-/
|
25
|
+
scan rest, arguments, merge(options, key)
|
26
|
+
else
|
27
|
+
value, *rest = rest
|
28
|
+
scan rest, arguments, merge(options, key, value)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
scan rest, arguments + [current], options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def switch?(key)
|
36
|
+
@switches.member? underscore key
|
37
|
+
end
|
38
|
+
|
39
|
+
def merge(options, key, value = nil)
|
40
|
+
key = underscore key
|
41
|
+
values = options[key] || []
|
42
|
+
options.merge(key => values + [value].compact)
|
43
|
+
end
|
44
|
+
|
45
|
+
def underscore(string)
|
46
|
+
string.gsub('-','_')
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "map"
|
2
|
+
|
3
|
+
class MVCLI::Decoding
|
4
|
+
def initialize
|
5
|
+
@enrichments = Map.new do |h,k|
|
6
|
+
h[k] = []
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(name, value, target = Object)
|
11
|
+
if target.is_a?(Array)
|
12
|
+
[value].flatten.map {|element| call name, element, target.first}
|
13
|
+
else
|
14
|
+
@enrichments[name].reduce value do |value, enrichment|
|
15
|
+
enrichment.call value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(name, &block)
|
21
|
+
fail "must supply a block to transform value named '#{name}'" unless block
|
22
|
+
@enrichments[name] << block
|
23
|
+
return self
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "mvcli/form"
|
2
|
+
require "active_support/inflector/methods"
|
3
|
+
|
4
|
+
class MVCLI::Form::Input
|
5
|
+
def initialize(name, target, options = {}, &block)
|
6
|
+
@decoders = []
|
7
|
+
@handler = handler(target).new name, target, options, &block
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode(&block)
|
11
|
+
@handler.decode &block
|
12
|
+
return self
|
13
|
+
end
|
14
|
+
|
15
|
+
def value(source, context = nil)
|
16
|
+
@handler.value source, context
|
17
|
+
end
|
18
|
+
|
19
|
+
def handler(target)
|
20
|
+
target.is_a?(Array) ? ListTarget : Target
|
21
|
+
end
|
22
|
+
|
23
|
+
class Target
|
24
|
+
def initialize(name, target, options = {}, &block)
|
25
|
+
@name, @options = name, Map(options)
|
26
|
+
@decoders = []
|
27
|
+
if block_given?
|
28
|
+
@decoders << block
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def decode(&block)
|
33
|
+
@decoders << block
|
34
|
+
end
|
35
|
+
|
36
|
+
def value(source, context = nil)
|
37
|
+
if value = [source[@name]].flatten.first
|
38
|
+
@decoders.reduce(value) do |value, decoder|
|
39
|
+
decoder.call value
|
40
|
+
end
|
41
|
+
else
|
42
|
+
default context
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def default(context)
|
47
|
+
value = @options[:default]
|
48
|
+
if value.respond_to?(:call)
|
49
|
+
if context
|
50
|
+
context.instance_exec(&value)
|
51
|
+
else
|
52
|
+
value.call
|
53
|
+
end
|
54
|
+
else
|
55
|
+
value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ListTarget < Target
|
61
|
+
include ActiveSupport::Inflector
|
62
|
+
|
63
|
+
def value(source, context = nil)
|
64
|
+
source = Map(source)
|
65
|
+
list = [source[singularize @name]].compact.flatten.map do |value|
|
66
|
+
super({@name => value}, context)
|
67
|
+
end.compact
|
68
|
+
list.empty? ? [default(context)].compact.flatten : list
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/mvcli/form.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "mvcli/decoding"
|
2
|
+
require "mvcli/validatable"
|
3
|
+
|
4
|
+
module MVCLI
|
5
|
+
class Form
|
6
|
+
include MVCLI::Validatable
|
7
|
+
|
8
|
+
def initialize(params = {}, type = Map)
|
9
|
+
@source = params
|
10
|
+
@target = type
|
11
|
+
end
|
12
|
+
|
13
|
+
def value
|
14
|
+
self.class.output.call self
|
15
|
+
end
|
16
|
+
|
17
|
+
def attributes
|
18
|
+
self.class.inputs.reduce(Map.new) do |map, pair|
|
19
|
+
name, input = *pair
|
20
|
+
map.tap do
|
21
|
+
map[name] = input.value @source, self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Decoder
|
27
|
+
def initialize(form, names)
|
28
|
+
@form, @names = form, names
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(string)
|
32
|
+
@form.new Map Hash[@names.zip string.split ':']
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_proc
|
36
|
+
proc {|*_| call(*_)}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
attr_accessor :target
|
42
|
+
attr_reader :inputs, :output
|
43
|
+
|
44
|
+
def inherited(base)
|
45
|
+
base.class_eval do
|
46
|
+
@inputs = Map.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def decoder
|
51
|
+
Decoder.new self, inputs.keys
|
52
|
+
end
|
53
|
+
|
54
|
+
def output(&block)
|
55
|
+
if block_given?
|
56
|
+
@output = block
|
57
|
+
else
|
58
|
+
@output
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def input(name, target, options = {}, &block)
|
63
|
+
if block_given?
|
64
|
+
form = Class.new(MVCLI::Form, &block)
|
65
|
+
form.target = [target].flatten.first
|
66
|
+
validates_child name
|
67
|
+
input = Input.new(name, target, options, &form.decoder)
|
68
|
+
else
|
69
|
+
input = Input.new(name, target, options, &options[:decode])
|
70
|
+
end
|
71
|
+
@inputs[name] = input
|
72
|
+
if options[:required]
|
73
|
+
if target.is_a?(Array)
|
74
|
+
validates(name, "cannot be empty", nil: true) {|value| value && !value.empty?}
|
75
|
+
else
|
76
|
+
validates(name, "is required", nil: true) {|value| !value.nil?}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
define_method(name) do
|
80
|
+
input.value @source, self
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
require "mvcli/form/input"
|
data/lib/mvcli/router.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "map"
|
2
|
-
|
2
|
+
require "mvcli/router/pattern"
|
3
|
+
require "mvcli/argv"
|
3
4
|
|
4
5
|
module MVCLI
|
5
6
|
class Router
|
@@ -33,7 +34,8 @@ module MVCLI
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def match(command)
|
36
|
-
|
37
|
+
argv = MVCLI::Argv.new command.argv
|
38
|
+
match = @pattern.match(argv.arguments)
|
37
39
|
if match.matches?
|
38
40
|
proc do |command|
|
39
41
|
action = @actions[@action] or fail "no action found for #{@action}"
|
@@ -42,9 +44,5 @@ module MVCLI
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
end
|
45
|
-
|
46
|
-
class Match
|
47
|
-
|
48
|
-
end
|
49
47
|
end
|
50
48
|
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require "map"
|
2
|
+
|
3
|
+
module MVCLI::Validatable
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ValidationDSL
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
validation.valid?
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate!
|
14
|
+
validation.validate!
|
15
|
+
end
|
16
|
+
|
17
|
+
def violations
|
18
|
+
validation.violations
|
19
|
+
end
|
20
|
+
|
21
|
+
def validation
|
22
|
+
validators.reduce(Validation.new(self)) do |validation, validator|
|
23
|
+
validation.tap do
|
24
|
+
validator.validate self, validation
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validators
|
30
|
+
self.class.validators
|
31
|
+
end
|
32
|
+
|
33
|
+
class ValidationError < StandardError
|
34
|
+
attr_reader :validation
|
35
|
+
|
36
|
+
def initialize(validation)
|
37
|
+
super validation.to_s
|
38
|
+
@validation = validation
|
39
|
+
end
|
40
|
+
|
41
|
+
def violations
|
42
|
+
validation.violations
|
43
|
+
end
|
44
|
+
|
45
|
+
def each(&block)
|
46
|
+
validation.each(&block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Validator
|
51
|
+
def initialize
|
52
|
+
@rules = []
|
53
|
+
@children = []
|
54
|
+
end
|
55
|
+
|
56
|
+
def validates(field, message, options = {}, &predicate)
|
57
|
+
@rules << Rule.new(field, message, Map(options), predicate)
|
58
|
+
end
|
59
|
+
|
60
|
+
def validates_child(name)
|
61
|
+
@children << name
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate(object, validation = Validation.new(object))
|
65
|
+
@rules.reduce(validation) do |v, rule|
|
66
|
+
v.tap do
|
67
|
+
rule.call object, v.violations, v.errors
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@children.each do |name|
|
71
|
+
validate_child object, name, validation
|
72
|
+
end
|
73
|
+
return validation
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_child(object, name, validation)
|
77
|
+
child = object.send(name) || []
|
78
|
+
validation.append name, [child].flatten.map(&:validation)
|
79
|
+
rescue StandardError => e
|
80
|
+
validation.errors[name] << e
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
class Validation
|
86
|
+
attr_reader :object, :violations, :errors
|
87
|
+
|
88
|
+
def initialize(object)
|
89
|
+
@object = object
|
90
|
+
@children = Map.new do |h,k|
|
91
|
+
h[k] = []
|
92
|
+
end
|
93
|
+
@violations = Map.new do |h,k|
|
94
|
+
h[k] = []
|
95
|
+
end
|
96
|
+
@errors = Map.new do |h,k|
|
97
|
+
h[k] = []
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def valid?
|
102
|
+
violations.empty? && errors.empty? && children_valid?
|
103
|
+
end
|
104
|
+
|
105
|
+
def validate!
|
106
|
+
fail ValidationError, self unless valid?
|
107
|
+
end
|
108
|
+
|
109
|
+
def children_valid?
|
110
|
+
@children.values.each do |validations|
|
111
|
+
return false unless validations.all?(&:valid?)
|
112
|
+
end
|
113
|
+
return true
|
114
|
+
end
|
115
|
+
|
116
|
+
def [](key)
|
117
|
+
@children[key]
|
118
|
+
end
|
119
|
+
|
120
|
+
def each(&block)
|
121
|
+
@children.each &block
|
122
|
+
end
|
123
|
+
|
124
|
+
def append(name, validations)
|
125
|
+
@children[name] += validations
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_s
|
129
|
+
elements = []
|
130
|
+
elements << "violations: #{@violations.inspect}" unless @violations.empty?
|
131
|
+
elements << "errors: #{@errors.inspect}" unless @errors.empty?
|
132
|
+
elements << "nested: #{children_to_s}" unless @children.empty?
|
133
|
+
[@object, elements.join(', ')].join ' '
|
134
|
+
end
|
135
|
+
|
136
|
+
def children_to_s
|
137
|
+
Hash[@children.keys.zip @children.values.map {|validations| validations.map(&:to_s)}].inspect
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module ValidationDSL
|
142
|
+
def validator
|
143
|
+
@validator ||= Validator.new
|
144
|
+
end
|
145
|
+
|
146
|
+
def validators
|
147
|
+
ancestors.reduce [] do |validators, base|
|
148
|
+
validators.tap do
|
149
|
+
validators << base.validator if base.respond_to?(:validator)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def validates(field, message, options = {}, &predicate)
|
155
|
+
validator.validates field, message, options, &predicate
|
156
|
+
end
|
157
|
+
|
158
|
+
def validates_not(field, message, &predicate)
|
159
|
+
validates(field, message) {|*_| !predicate.call(*_)}
|
160
|
+
end
|
161
|
+
|
162
|
+
def validates_child(name)
|
163
|
+
validator.validates_child name
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class Rule
|
168
|
+
def initialize(field, message, options, predicate)
|
169
|
+
@field, @message, @options, @predicate = field, message, options, predicate
|
170
|
+
end
|
171
|
+
def call(validatable, violations, errors)
|
172
|
+
value, error = read validatable
|
173
|
+
if error
|
174
|
+
errors[@field] << error
|
175
|
+
elsif value.nil?
|
176
|
+
return unless !!@options[:nil]
|
177
|
+
else
|
178
|
+
violations[@field] << @message unless @predicate.call value
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def read(validatable)
|
183
|
+
return validatable.send(@field), nil
|
184
|
+
rescue StandardError => e
|
185
|
+
return nil, e
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
data/lib/mvcli/version.rb
CHANGED
data/lib/mvcli.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "mvcli/argv"
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
describe "ARGV" do
|
6
|
+
use_natural_assertions
|
7
|
+
Given(:input) {Shellwords.shellsplit "before --one 1 --two=two stuff in the middle --three -f --four --no-five -p 6 --mo money --mo problems --two-word val --two-word-p after"}
|
8
|
+
Given(:argv) {MVCLI::Argv.new input, [:three, :f, :five, :two_word_p]}
|
9
|
+
|
10
|
+
context " options" do
|
11
|
+
Given(:options) {argv.options}
|
12
|
+
Then {options[:one] == ['1']}
|
13
|
+
Then {options[:two] == ['two']}
|
14
|
+
Then {options[:three] == [true]}
|
15
|
+
Then {options[:f] == [true]}
|
16
|
+
Then {options[:four] == []}
|
17
|
+
Then {options[:five] == [false]}
|
18
|
+
Then {options[:p] == ['6']}
|
19
|
+
Then {options[:mo] == ['money', 'problems']}
|
20
|
+
Then {options[:two_word] == ['val']}
|
21
|
+
Then {options[:two_word_p] == [true]}
|
22
|
+
end
|
23
|
+
context " aruments" do
|
24
|
+
Given(:arguments) {argv.arguments}
|
25
|
+
Then {arguments == %w(before stuff in the middle after)}
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "mvcli/form/input"
|
3
|
+
|
4
|
+
describe "Form Inputs" do
|
5
|
+
use_natural_assertions
|
6
|
+
Given(:input) {MVCLI::Form::Input.new :field, type, options, &block}
|
7
|
+
Given(:type) {Object}
|
8
|
+
Given(:options) {{}}
|
9
|
+
Given(:block) {nil}
|
10
|
+
describe "with an integer type" do
|
11
|
+
Given(:type) {Integer}
|
12
|
+
context "when accessing a single value" do
|
13
|
+
When(:value) {input.value field: 5}
|
14
|
+
Then {value == 5}
|
15
|
+
end
|
16
|
+
context "when accessing an array of values" do
|
17
|
+
When(:value) {input.value field: [1,2,3]}
|
18
|
+
Then {value == 1}
|
19
|
+
end
|
20
|
+
context "when accessing a nil value" do
|
21
|
+
When(:value) {input.value field: nil}
|
22
|
+
Then {value.nil?}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
describe "with a list of integers" do
|
26
|
+
Given(:type) {[Integer]}
|
27
|
+
context "when accessing an value represented as a single" do
|
28
|
+
When(:value) {input.value field: 5}
|
29
|
+
Then {value == [5]}
|
30
|
+
end
|
31
|
+
context "when accessing a value represented as an array" do
|
32
|
+
When(:value) {input.value field: [1,2,3]}
|
33
|
+
Then {value == [1,2,3]}
|
34
|
+
end
|
35
|
+
context "when accessing a nil value" do
|
36
|
+
When(:value) {input.value field: nil}
|
37
|
+
Then {value == []}
|
38
|
+
end
|
39
|
+
describe "with decoding" do
|
40
|
+
Given(:block) {->(s) { Integer s * 2 } }
|
41
|
+
context "with singular value" do
|
42
|
+
When(:value) {input.value field: '1'}
|
43
|
+
Then {value == [11]}
|
44
|
+
end
|
45
|
+
context "with array value" do
|
46
|
+
When(:value) {input.value field: ['1', '2']}
|
47
|
+
Then {value == [11,22]}
|
48
|
+
end
|
49
|
+
context "with a sparse array value" do
|
50
|
+
When(:value) {input.value field: ['1', nil, '2']}
|
51
|
+
Then {value == [11,22]}
|
52
|
+
end
|
53
|
+
context "with an empty list" do
|
54
|
+
When(:value) {input.value field: []}
|
55
|
+
Then {value == []}
|
56
|
+
end
|
57
|
+
context "with a nil value" do
|
58
|
+
When(:value) {input.value field: nil}
|
59
|
+
Then {value == []}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
describe "with decoding" do
|
64
|
+
Given(:block) { ->(s) { Integer s * 2}}
|
65
|
+
|
66
|
+
context "when accessed" do
|
67
|
+
When(:value) {input.value field: ['1']}
|
68
|
+
Then {value == 11}
|
69
|
+
end
|
70
|
+
context "when accessing a nil value and no default" do
|
71
|
+
When(:value) {input.value field: nil}
|
72
|
+
Then {value.nil?}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "with a default" do
|
77
|
+
Given(:options) {{default: 5}}
|
78
|
+
context "when accesing nil" do
|
79
|
+
When(:value) {input.value field: nil}
|
80
|
+
Then {value == 5}
|
81
|
+
end
|
82
|
+
context "when accessing a value" do
|
83
|
+
When(:value) {input.value field: 10}
|
84
|
+
Then {value == 10}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "a plural list input" do
|
89
|
+
Given(:input) {MVCLI::Form::Input.new :fields, [Integer]}
|
90
|
+
context "passing a singular value" do
|
91
|
+
When(:value) { input.value field: 10 }
|
92
|
+
Then {value == [10]}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "mvcli/form"
|
3
|
+
require "mvcli/decoding"
|
4
|
+
require "ipaddr"
|
5
|
+
|
6
|
+
describe "A form for creating a load balancer" do
|
7
|
+
use_natural_assertions
|
8
|
+
Given(:definition) do
|
9
|
+
Class.new(MVCLI::Form) do
|
10
|
+
input :name, String, default: -> {naming.generate 'l', 'b'}
|
11
|
+
|
12
|
+
input :port, Integer, default: 80, decode: ->(s) {Integer s}
|
13
|
+
|
14
|
+
input :protocol, String, default: 'HTTP', decode: :upcase
|
15
|
+
|
16
|
+
input :virtual_ips, [String], default: ['PUBLIC']
|
17
|
+
|
18
|
+
input :nodes, [Node], required: true do
|
19
|
+
input :address, IPAddr, required: true, decode: ->(s) {IPAddr.new s}
|
20
|
+
input :port, Integer, default: 80, decode: ->(s) {Integer s}
|
21
|
+
input :type, String, default: 'PRIMARY', decode: :upcase
|
22
|
+
input :condition, String, default: 'ENABLED', decode: :upcase
|
23
|
+
|
24
|
+
validates(:port, "port must be between 0 and 65,535") {|port| port >= 0 && port <= 65535}
|
25
|
+
validates(:type, "invalid type") {|type| ['PRIMARY', 'SECONDARY'].member? type}
|
26
|
+
validates(:condition, "invalid condition") {|c| ['ENABLED', 'DISABLED'].member? c}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
Given(:form) do
|
31
|
+
definition.new(params).tap do |f|
|
32
|
+
f.stub(:decoders) {MVCLI::Decoding}
|
33
|
+
f.stub(:naming) {mock(:NameGenerator, generate: 'random-name')}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
context "with no nodes provided" do
|
37
|
+
Given(:params) {({node: []})}
|
38
|
+
Then {!form.valid?}
|
39
|
+
And {form.violations[:nodes] == ["cannot be empty"]}
|
40
|
+
|
41
|
+
end
|
42
|
+
context "with invalid node inputs" do
|
43
|
+
Given(:params) do
|
44
|
+
({
|
45
|
+
node: ['10.0.0.1:-500', 'xxx:80']
|
46
|
+
})
|
47
|
+
end
|
48
|
+
Then {!form.valid?}
|
49
|
+
context "the first violation" do
|
50
|
+
Given(:violations) {form.validation[:nodes].first.violations}
|
51
|
+
Then {violations[:port] == ["port must be between 0 and 65,535"]}
|
52
|
+
end
|
53
|
+
context "the second error" do
|
54
|
+
Given(:errors) {form.validation[:nodes].last.errors}
|
55
|
+
Then {errors[:address].first.is_a?(IPAddr::InvalidAddressError)}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context "with partially specified, valid inputs" do
|
59
|
+
Given(:params) do
|
60
|
+
({node: ['10.0.0.1:80']})
|
61
|
+
end
|
62
|
+
Then {form.name == 'random-name'}
|
63
|
+
And {form.port == 80}
|
64
|
+
And {form.protocol == 'HTTP'}
|
65
|
+
And {form.virtual_ips == ['PUBLIC']}
|
66
|
+
context "the default form node" do
|
67
|
+
Given(:node) {form.nodes.first}
|
68
|
+
Then {node.address == IPAddr.new('10.0.0.1')}
|
69
|
+
And {node.port == 80}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
context "with fully specified, valid inputs" do
|
73
|
+
Given(:params) {
|
74
|
+
({
|
75
|
+
name: 'foo',
|
76
|
+
port: '80',
|
77
|
+
protocol: 'http',
|
78
|
+
virtual_ip: ['public', 'servicenet'],
|
79
|
+
node: [
|
80
|
+
'10.0.0.1:80:primary:enabled',
|
81
|
+
'10.0.0.2:80:secondary:disabled'
|
82
|
+
]
|
83
|
+
})
|
84
|
+
}
|
85
|
+
Then {form.valid?}
|
86
|
+
And {form.name == 'foo'}
|
87
|
+
And {form.port == 80}
|
88
|
+
And {form.protocol == 'HTTP'}
|
89
|
+
And {form.nodes.length == 2}
|
90
|
+
context ". On the first node" do
|
91
|
+
Given(:node) {form.nodes.first}
|
92
|
+
Then {node.address == IPAddr.new('10.0.0.1')}
|
93
|
+
And {node.port == 80}
|
94
|
+
And {node.condition == 'ENABLED'}
|
95
|
+
And {node.type == 'PRIMARY'}
|
96
|
+
end
|
97
|
+
context ". On the second node" do
|
98
|
+
Given(:node) {form.nodes.last}
|
99
|
+
Then {node.address == IPAddr.new('10.0.0.2')}
|
100
|
+
And {node.port == 80}
|
101
|
+
And {node.condition == 'DISABLED'}
|
102
|
+
And {node.type == 'SECONDARY'}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Node
|
108
|
+
include MVCLI::Validatable
|
109
|
+
attr_accessor :address, :port, :protocol, :condition, :type
|
110
|
+
validates(:port, "port must be between 0 and 65,535") {|port| port >= 0 && port <= 65535}
|
111
|
+
|
112
|
+
def initialize(attrs)
|
113
|
+
@address, @port, @protocal, @condition, @type = *attrs.values_at(:address, :port, :protocol, :condition, :type)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
data/spec/mvcli/router_spec.rb
CHANGED
@@ -25,6 +25,12 @@ describe "MVCLI::Router" do
|
|
25
25
|
Then {@command.argv.should eql ['login']}
|
26
26
|
end
|
27
27
|
|
28
|
+
context "when there are command line options, it does not interfere" do
|
29
|
+
Given {router.match 'login' => 'logins#create'}
|
30
|
+
When {invoke 'login --then --go-away -f 6 -p'}
|
31
|
+
Then {@command.should_not be_nil}
|
32
|
+
end
|
33
|
+
|
28
34
|
context "with a route matched to a block" do
|
29
35
|
Given {router.match bam: ->(command) {@command = command}}
|
30
36
|
When {invoke 'bam'}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "mvcli/validatable"
|
3
|
+
|
4
|
+
describe "a validator" do
|
5
|
+
Given(:validator) {MVCLI::Validatable::Validator.new}
|
6
|
+
context "when it validates a field that does not exist on the object" do
|
7
|
+
Given {validator.validates(:does_not_exist, "invalid") {}}
|
8
|
+
When(:validation) {validator.validate(Object.new)}
|
9
|
+
Then {validation.errors[:does_not_exist].class < NameError}
|
10
|
+
Then {not validation.valid?}
|
11
|
+
end
|
12
|
+
describe "validating a child" do
|
13
|
+
Given {validator.validates_child(:some_child)}
|
14
|
+
context "when it is nil" do
|
15
|
+
When(:validation) {validator.validate(mock(:Object, :some_child => nil))}
|
16
|
+
Then {validation.valid?}
|
17
|
+
end
|
18
|
+
context "when it does not exist" do
|
19
|
+
When(:validation) {validator.validate(Object.new)}
|
20
|
+
Then {not validation.errors[:some_child].nil?}
|
21
|
+
And {not validation.valid?}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mvcli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Lowell
|
@@ -29,7 +29,7 @@ cert_chain:
|
|
29
29
|
UgImJlChAzCoDP9zi9tdm6jAr7ttF25R9PPYr11ILb7dYe3qUzlNlM6zJx/nb31b
|
30
30
|
IhdyRVup4qLcqYSTPsm6u7VA
|
31
31
|
-----END CERTIFICATE-----
|
32
|
-
date: 2013-06-
|
32
|
+
date: 2013-06-26 00:00:00.000000000 Z
|
33
33
|
dependencies:
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: map
|
@@ -72,14 +72,16 @@ files:
|
|
72
72
|
- LICENSE.txt
|
73
73
|
- README.md
|
74
74
|
- Rakefile
|
75
|
-
- example/controllers/loadbalancers_controller.rb
|
76
|
-
- example/routes.rb
|
77
75
|
- lib/mvcli.rb
|
78
76
|
- lib/mvcli/actions.rb
|
79
77
|
- lib/mvcli/app.rb
|
78
|
+
- lib/mvcli/argv.rb
|
80
79
|
- lib/mvcli/command.rb
|
81
80
|
- lib/mvcli/controller.rb
|
81
|
+
- lib/mvcli/decoding.rb
|
82
82
|
- lib/mvcli/erb.rb
|
83
|
+
- lib/mvcli/form.rb
|
84
|
+
- lib/mvcli/form/input.rb
|
83
85
|
- lib/mvcli/loader.rb
|
84
86
|
- lib/mvcli/middleware.rb
|
85
87
|
- lib/mvcli/middleware/exception_logger.rb
|
@@ -88,11 +90,15 @@ files:
|
|
88
90
|
- lib/mvcli/renderer.rb
|
89
91
|
- lib/mvcli/router.rb
|
90
92
|
- lib/mvcli/router/pattern.rb
|
93
|
+
- lib/mvcli/validatable.rb
|
91
94
|
- lib/mvcli/version.rb
|
92
95
|
- mvcli.gemspec
|
93
96
|
- spec/mvcli/actions_spec.rb
|
97
|
+
- spec/mvcli/argv_spec.rb
|
94
98
|
- spec/mvcli/dummy/app/providers/test_provider.rb
|
95
99
|
- spec/mvcli/erb_spec.rb
|
100
|
+
- spec/mvcli/form/input_spec.rb
|
101
|
+
- spec/mvcli/form_spec.rb
|
96
102
|
- spec/mvcli/loader_spec.rb
|
97
103
|
- spec/mvcli/middleware/exception_logger_spec.rb
|
98
104
|
- spec/mvcli/middleware/exit_status_spec.rb
|
@@ -100,6 +106,7 @@ files:
|
|
100
106
|
- spec/mvcli/provisioning_spec.rb
|
101
107
|
- spec/mvcli/router/pattern_spec.rb
|
102
108
|
- spec/mvcli/router_spec.rb
|
109
|
+
- spec/mvcli/validatable_spec.rb
|
103
110
|
- spec/spec_helper.rb
|
104
111
|
homepage: https://github.com/cowboyd/mvcli
|
105
112
|
licenses:
|
@@ -127,8 +134,11 @@ specification_version: 4
|
|
127
134
|
summary: Local Apps. Remote Apps. They're all at your fingertips
|
128
135
|
test_files:
|
129
136
|
- spec/mvcli/actions_spec.rb
|
137
|
+
- spec/mvcli/argv_spec.rb
|
130
138
|
- spec/mvcli/dummy/app/providers/test_provider.rb
|
131
139
|
- spec/mvcli/erb_spec.rb
|
140
|
+
- spec/mvcli/form/input_spec.rb
|
141
|
+
- spec/mvcli/form_spec.rb
|
132
142
|
- spec/mvcli/loader_spec.rb
|
133
143
|
- spec/mvcli/middleware/exception_logger_spec.rb
|
134
144
|
- spec/mvcli/middleware/exit_status_spec.rb
|
@@ -136,4 +146,6 @@ test_files:
|
|
136
146
|
- spec/mvcli/provisioning_spec.rb
|
137
147
|
- spec/mvcli/router/pattern_spec.rb
|
138
148
|
- spec/mvcli/router_spec.rb
|
149
|
+
- spec/mvcli/validatable_spec.rb
|
139
150
|
- spec/spec_helper.rb
|
151
|
+
has_rdoc:
|
data/example/routes.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
collection :loadbalancers do
|
2
|
-
# match "create loadbalancer"
|
3
|
-
# match "show loadbalancers"
|
4
|
-
# match "loadbalancers"
|
5
|
-
# match "show loadbalancer :id" #=> 'loadbalancers#show'
|
6
|
-
# match "update loadbalancer :id #=> loadbalancers#update"
|
7
|
-
# match "destroy loadbalancer :id"
|
8
|
-
collection :nodes do
|
9
|
-
# match "create node on loadbalancer :loadbalancer_id"
|
10
|
-
# match "create loadbalancer :loadbalancer_id node"
|
11
|
-
# match "show nodes on loadbalancer :loadbalancer_id"
|
12
|
-
# match "loadbalancer nodes"
|
13
|
-
# match "show loadbalancer :loadbalancer_id nodes"
|
14
|
-
# match "show loadbalancer :loadbalancer_id node :id"
|
15
|
-
# match "show node :id on loadbalancer :loadbalancer_id"
|
16
|
-
# match "update loadbalancer:node"
|
17
|
-
# match "destroy loadbalancer:node:$id"
|
18
|
-
# match "destroy loadbalancer node $id"
|
19
|
-
# match "destroy loadbalancer:node $id"
|
20
|
-
# match "help "
|
21
|
-
end
|
22
|
-
|
23
|
-
collection :virtual_ips, :only => :index
|
24
|
-
# match "show virtual_ips on loadbalancer :loadbalancer_id"
|
25
|
-
# match "show loadbalancer :loadbalancer_id virtual_ips"
|
26
|
-
# match "loadbalancers"
|
27
|
-
end
|
28
|
-
|
29
|
-
collection :servers do
|
30
|
-
|
31
|
-
end
|