mvcli 0.0.6 → 0.0.7
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/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
|