mvcli 0.0.16 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +4 -0
- data/Gemfile +2 -2
- data/README.md +6 -2
- data/app.rb +7 -0
- data/app/routes.rb +0 -0
- data/bin/mvcli +5 -0
- data/lib/mvcli.rb +1 -0
- data/lib/mvcli/action.rb +31 -0
- data/lib/mvcli/app.rb +23 -28
- data/lib/mvcli/controller.rb +20 -2
- data/lib/mvcli/core.rb +101 -0
- data/lib/mvcli/cortex.rb +38 -0
- data/lib/mvcli/form.rb +13 -0
- data/lib/mvcli/loader.rb +45 -1
- data/lib/mvcli/middleware.rb +13 -15
- data/lib/mvcli/path.rb +41 -0
- data/lib/mvcli/plugins.rb +19 -0
- data/lib/mvcli/plugins/controllers/plugins_controller.rb +23 -0
- data/lib/mvcli/plugins/forms/plugins/install_form.rb +6 -0
- data/lib/mvcli/plugins/models/plugins/installation_model.rb +33 -0
- data/lib/mvcli/plugins/providers/bundle_provider.rb +116 -0
- data/lib/mvcli/plugins/routes.rb +3 -0
- data/lib/mvcli/plugins/templates/plugins/index.txt.erb +3 -0
- data/lib/mvcli/plugins/templates/plugins/install.txt.erb +1 -0
- data/lib/mvcli/plugins/templates/plugins/uninstall.txt.erb +1 -0
- data/lib/mvcli/provisioning.rb +48 -38
- data/lib/mvcli/router.rb +34 -20
- data/lib/mvcli/std/extensions/erb_extension.rb +24 -0
- data/lib/mvcli/std/providers/argv_provider.rb +9 -0
- data/lib/mvcli/std/providers/config_provider.rb +29 -0
- data/lib/mvcli/std/providers/middleware_provider.rb +12 -0
- data/lib/mvcli/std/providers/router_provider.rb +17 -0
- data/lib/mvcli/std/routes.rb +2 -0
- data/lib/mvcli/version.rb +1 -1
- data/spec/features/managing_plugins_spec.rb +29 -0
- data/spec/fixtures/apps/trivium/app.rb +10 -0
- data/spec/fixtures/apps/trivium/app/routes.rb +0 -0
- data/spec/fixtures/apps/trivium/bin/trivium +6 -0
- data/spec/fixtures/apps/trivium/lib/trivium/version.rb +3 -0
- data/spec/fixtures/apps/trivium/trivium.gemspec +8 -0
- data/spec/fixtures/bin/trivium +3 -0
- data/spec/fixtures/plugins/timing-plugin/app/routes.rb +1 -0
- data/spec/fixtures/plugins/timing-plugin/lib/trivium-timing.rb +5 -0
- data/spec/fixtures/plugins/timing-plugin/trivium-timing.gemspec +9 -0
- data/spec/mvcli/action_spec.rb +31 -0
- data/spec/mvcli/controller_spec.rb +35 -0
- data/spec/mvcli/core_spec.rb +77 -0
- data/spec/mvcli/cortex_spec.rb +50 -0
- data/spec/mvcli/erb_spec.rb +1 -1
- data/spec/mvcli/form_spec.rb +1 -1
- data/spec/mvcli/loader_spec.rb +58 -13
- data/spec/mvcli/middleware/exception_logger_spec.rb +3 -3
- data/spec/mvcli/middleware/exit_status_spec.rb +10 -10
- data/spec/mvcli/middleware_spec.rb +28 -45
- data/spec/mvcli/path_spec.rb +13 -0
- data/spec/mvcli/path_spec/does/exist +1 -0
- data/spec/mvcli/plugins/providers/bundle_provider_spec.rb +23 -0
- data/spec/mvcli/provisioning_spec.rb +39 -37
- data/spec/mvcli/router_spec.rb +26 -32
- data/spec/mvcli/std/extensions/erb_extension_spec.rb +34 -0
- data/spec/mvcli/std/providers/middleware_provider_spec.rb +10 -0
- data/spec/mvcli/validatable_spec.rb +12 -12
- data/spec/spec_helper.rb +13 -1
- data/spec/support/aruba_helper.rb +76 -0
- metadata +69 -33
- data/lib/mvcli/actions.rb +0 -37
- data/lib/mvcli/renderer.rb +0 -16
- data/spec/mvcli/actions_spec.rb +0 -34
- data/spec/mvcli/dummy/app/providers/test_provider.rb +0 -5
@@ -0,0 +1,24 @@
|
|
1
|
+
require "mvcli/erb"
|
2
|
+
require "active_support/inflector"
|
3
|
+
class MVCLI::ERBExtension
|
4
|
+
include ActiveSupport::Inflector
|
5
|
+
|
6
|
+
def to_path(name, extension_type)
|
7
|
+
check! extension_type
|
8
|
+
"#{pluralize extension_type}/#{name}.txt.erb"
|
9
|
+
end
|
10
|
+
|
11
|
+
def define(name, bytes, extension_type, namespace)
|
12
|
+
check! extension_type
|
13
|
+
erb = MVCLI::ERB.new
|
14
|
+
erb.compile bytes, to_path(name, extension_type)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def check!(extension_type)
|
20
|
+
if extension_type.to_s != "template"
|
21
|
+
fail ArgumentError, "invalid extension type '#{extension_type}' for ERB"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
class MVCLI::ConfigProvider
|
4
|
+
requires :app
|
5
|
+
|
6
|
+
def self.value
|
7
|
+
new
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :home
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@home = find_or_create "#{ENV['HOME']}/.#{app.name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def directory(name)
|
17
|
+
pathname = find_or_create @home.join name
|
18
|
+
yield pathname if block_given?
|
19
|
+
return pathname
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def find_or_create(path)
|
25
|
+
Pathname(path.to_s).tap do |path|
|
26
|
+
FileUtils.mkdir_p path.to_s unless path.exist?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "mvcli/middleware"
|
2
|
+
require "mvcli/middleware/exit_status"
|
3
|
+
require "mvcli/middleware/exception_logger"
|
4
|
+
|
5
|
+
class MVCLI::MiddlewareProvider
|
6
|
+
def value
|
7
|
+
MVCLI::Middleware.new do |middleware|
|
8
|
+
middleware << MVCLI::Middleware::ExitStatus.new
|
9
|
+
middleware << MVCLI::Middleware::ExceptionLogger.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "mvcli/router"
|
2
|
+
|
3
|
+
module MVCLI
|
4
|
+
class RouterProvider
|
5
|
+
requires :cortex
|
6
|
+
|
7
|
+
def value
|
8
|
+
builder = Router::DSL.new
|
9
|
+
cortex.each do |core|
|
10
|
+
if core.path.exists? 'routes.rb'
|
11
|
+
builder.instance_eval core.path.read('routes.rb'), core.path.to_s('routes.rb'), 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
return builder.router
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/mvcli/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "installing a plugin" do
|
4
|
+
before { @timeout = 60 }
|
5
|
+
When { r "trivium install plugin trivium-timing --path #{fixture 'plugins/timing-plugin'}" }
|
6
|
+
Given(:fixtures) { Pathname(__FILE__).dirname.join('') }
|
7
|
+
|
8
|
+
Then { plugin_list == ['trivium-timing'] }
|
9
|
+
And { trivial_timing == '1s'}
|
10
|
+
|
11
|
+
describe "uninstalling the plugin" do
|
12
|
+
When { r "trivium uninstall plugin trivium-timing" }
|
13
|
+
Then { plugin_list == [] }
|
14
|
+
describe "trying to invoke the command" do
|
15
|
+
When(:result) { r "trivium time" }
|
16
|
+
Then { result.should have_failed }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def plugin_list
|
21
|
+
r "trivium show plugins"
|
22
|
+
all_stdout.split "\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def trivial_timing
|
26
|
+
r "trivium time"
|
27
|
+
all_stdout
|
28
|
+
end
|
29
|
+
end
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
match 'time' => proc { |cmd| cmd.output.puts '1s' }
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "mvcli/action"
|
3
|
+
|
4
|
+
describe "MVCLI::Action" do
|
5
|
+
use_natural_assertions
|
6
|
+
|
7
|
+
Given(:command) { double :Command }
|
8
|
+
Given(:cortex) { double :Cortex }
|
9
|
+
Given(:action) { MVCLI::Action.new match, mapping }
|
10
|
+
Given(:match) { double :Match, :bindings => double(:Bindings) }
|
11
|
+
Given { action.stub(:cortex) { cortex } }
|
12
|
+
Given { action.stub(:middleware) { ->(cmd, &block) { block.call Map cmd: cmd } } }
|
13
|
+
|
14
|
+
context "when the mapping is callable" do
|
15
|
+
Given(:mapping) { ->(command) { command } }
|
16
|
+
When(:result) { action.call command }
|
17
|
+
Then { result.cmd == command }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when the mapping is a string" do
|
21
|
+
Given(:mapping) { 'servers#show' }
|
22
|
+
Given(:controller) { double(:Controller, call: Object.new) }
|
23
|
+
Given(:controller_class) { double :ControllerClass, new: controller }
|
24
|
+
Given { cortex.stub(:read) { controller_class } }
|
25
|
+
|
26
|
+
When(:result) { action.call command }
|
27
|
+
Then { result == controller.call }
|
28
|
+
Then { cortex.should have_received(:read).with(:controller, 'servers') }
|
29
|
+
Then { controller_class.should have_received(:new).with 'servers', 'show', match.bindings }
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "mvcli/controller"
|
3
|
+
|
4
|
+
describe "A Controller" do
|
5
|
+
use_natural_assertions
|
6
|
+
|
7
|
+
Given(:controller) { MVCLI::Controller.new name, method, params }
|
8
|
+
Given(:cortex) { double(:Cortex) }
|
9
|
+
Given(:command) { double(:Command, output: StringIO.new ) }
|
10
|
+
Given { controller.stub(:cortex) { cortex } }
|
11
|
+
Given { cortex.stub(:read) { proc { |context, output| @context, @output = context, output} } }
|
12
|
+
|
13
|
+
context "when called with the 'show' action" do
|
14
|
+
Given(:name) { 'servers' }
|
15
|
+
Given(:method) { 'show' }
|
16
|
+
Given(:params) { Map name: 'World' }
|
17
|
+
Given { controller.stub(:show) { params } }
|
18
|
+
When(:exit_status) { controller.call command }
|
19
|
+
Then { exit_status == 0 }
|
20
|
+
Then { @output == command.output }
|
21
|
+
And { cortex.should have_received(:read).with :template, "servers/show"}
|
22
|
+
|
23
|
+
context "when there's a corresponding form" do
|
24
|
+
Given { controller.stub(:argv) { double(:argv, options: {}) } }
|
25
|
+
Given { cortex.stub(:exists?).with(:form, "servers/show") { true } }
|
26
|
+
Given { cortex.stub(:read).with(:form, "servers/show") { double(:Form, new: form) } }
|
27
|
+
Given{ form.stub(:validate!) { true } }
|
28
|
+
Given(:form) { double :form }
|
29
|
+
Then{ controller.form == form }
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when there's not a corresponding form" do
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "MVCLI Cores" do
|
4
|
+
use_natural_assertions
|
5
|
+
|
6
|
+
Given(:loader) { double(:Loader) }
|
7
|
+
Given { core.stub(:loader) { loader } }
|
8
|
+
Given { loader.stub(:exists?) { true } }
|
9
|
+
|
10
|
+
|
11
|
+
describe "with explicit values for path and namespace" do
|
12
|
+
Given(:core) { MVCLI::Core.new path: path, namespace: namespace}
|
13
|
+
Given(:path) { MVCLI::Path.new '/path/to/the/core' }
|
14
|
+
Given(:namespace) { Module.new }
|
15
|
+
|
16
|
+
|
17
|
+
describe "the query interface" do
|
18
|
+
Then { core.exists? :provider, 'naming' }
|
19
|
+
And { loader.should have_received(:exists?).with(path, :provider, 'naming') }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "reading the extension into memory" do
|
23
|
+
context "when it does not exist" do
|
24
|
+
Given { loader.stub(:exists?) { false } }
|
25
|
+
When(:result) { core.read :provider, 'naming' }
|
26
|
+
Then { result.should have_failed MVCLI::ExtensionNotFound }
|
27
|
+
end
|
28
|
+
context "when it does exist" do
|
29
|
+
Given(:artifact) { Class.new }
|
30
|
+
Given { loader.stub(:read) { artifact } }
|
31
|
+
When(:result) { core.read :provider, 'naming' }
|
32
|
+
Then { loader.should have_received(:read).with(path, :provider, 'naming', namespace) }
|
33
|
+
And { result == artifact }
|
34
|
+
And { result.core == core }
|
35
|
+
And { result.new.core == core }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "with no explicit values for path and namespace" do
|
41
|
+
Given(:core) { MVCLI::Core.new }
|
42
|
+
When(:result) { core.path }
|
43
|
+
Then { result.should have_failed MVCLI::InvalidPath }
|
44
|
+
Then { core.namespace == Object }
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "resolving the namespace" do
|
48
|
+
When(:namespace) { core.namespace }
|
49
|
+
Invariant { MVCLI::Core.member? core.class }
|
50
|
+
|
51
|
+
describe "when the class is anonymous" do
|
52
|
+
Given(:core) { Class.new(MVCLI::Core).new }
|
53
|
+
Then { namespace == Object }
|
54
|
+
describe "but the namespace attribute is set" do
|
55
|
+
Given { core.class.namespace = MVCLI }
|
56
|
+
Then { namespace == MVCLI }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
describe "when the class is named" do
|
60
|
+
Given(:core) do
|
61
|
+
module MVCLI
|
62
|
+
module Test
|
63
|
+
class MyCore < MVCLI::Core
|
64
|
+
new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
Then { namespace == MVCLI::Test }
|
70
|
+
And { core.name == 'mvcli-test-mycore' }
|
71
|
+
describe "but the namespace class attribute is set" do
|
72
|
+
Given { core.class.namespace = MVCLI }
|
73
|
+
Then { namespace == MVCLI }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "mvcli/cortex"
|
3
|
+
|
4
|
+
describe "A Cortex" do
|
5
|
+
use_natural_assertions
|
6
|
+
Given(:cortex) { MVCLI::Cortex.new }
|
7
|
+
|
8
|
+
describe "without any cores" do
|
9
|
+
When(:result) { cortex.read :extension, 'foo/bar/baz' }
|
10
|
+
Then { result.should have_failed MVCLI::ExtensionNotFound }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "with a few cores" do
|
14
|
+
Given(:core1) { add_core :Core1 }
|
15
|
+
Given(:core2) { add_core :Core2 }
|
16
|
+
|
17
|
+
context "when we access an object" do
|
18
|
+
When(:extension) { cortex.read :controller, "admin/users" }
|
19
|
+
|
20
|
+
context "and it is defined and exists" do
|
21
|
+
Given { core1.stub(:read) { double(:Extension, core: 1) } }
|
22
|
+
Given { core2.stub(:read) { double(:Extension, core: 2) } }
|
23
|
+
|
24
|
+
Invariant { cortex.exists? :controller, "admin/users"}
|
25
|
+
|
26
|
+
context "only in the first core" do
|
27
|
+
Given { core1.stub(:exists?) { true } }
|
28
|
+
Then { extension.core == 1}
|
29
|
+
end
|
30
|
+
context "only in the second core" do
|
31
|
+
Given { core1.stub(:exists?) { false } }
|
32
|
+
Given { core2.stub(:exists?) { true } }
|
33
|
+
Then { extension.core == 2 }
|
34
|
+
end
|
35
|
+
context "in both cores" do
|
36
|
+
Given { core1.stub(:exists?) { true} }
|
37
|
+
Given { core2.stub(:exists?) { true } }
|
38
|
+
Then { core1.should have_received(:read) }
|
39
|
+
Then { core2.should_not have_received(:read) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_core(name)
|
46
|
+
core = double name
|
47
|
+
cortex << core
|
48
|
+
return core
|
49
|
+
end
|
50
|
+
end
|
data/spec/mvcli/erb_spec.rb
CHANGED
@@ -7,7 +7,7 @@ describe "MVCLI::ERB" do
|
|
7
7
|
Given(:output) {""}
|
8
8
|
Given(:template) {erb.compile "name: <%= this.name %>"}
|
9
9
|
context "and call it with a context" do
|
10
|
-
When {template.call
|
10
|
+
When {template.call double(:Context, :name => 'Charles'), output}
|
11
11
|
Then {output.should eql "name: Charles"}
|
12
12
|
end
|
13
13
|
end
|
data/spec/mvcli/form_spec.rb
CHANGED
@@ -31,7 +31,7 @@ describe "A form for creating a load balancer" do
|
|
31
31
|
Given(:form) do
|
32
32
|
definition.new(params).tap do |f|
|
33
33
|
f.stub(:decoders) {MVCLI::Decoding}
|
34
|
-
f.stub(:naming) {
|
34
|
+
f.stub(:naming) {double(:NameGenerator, generate: 'random-name')}
|
35
35
|
end
|
36
36
|
end
|
37
37
|
context "with invalid array properties" do
|
data/spec/mvcli/loader_spec.rb
CHANGED
@@ -2,22 +2,67 @@ require "spec_helper"
|
|
2
2
|
require "mvcli/loader"
|
3
3
|
|
4
4
|
describe "MVCLI::Loader" do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
use_natural_assertions
|
6
|
+
Given(:extensions) { Hash.new }
|
7
|
+
Given(:path) { double :Path }
|
8
|
+
Given(:loader) { MVCLI::Loader.new extensions }
|
9
|
+
Given { path.stub(:exists?) { true } }
|
10
|
+
Given { path.stub(:read) { content }}
|
11
|
+
|
12
|
+
describe "querying if an extension exists" do
|
13
|
+
context "when it does not exists in the path" do
|
14
|
+
Given { path.stub(:exists?) { false } }
|
15
|
+
Then { not loader.exists? path, :provider, 'bar' }
|
16
|
+
And { path.should have_received(:exists?).with 'providers/bar_provider.rb' }
|
17
|
+
end
|
18
|
+
context "when it does exist in the path" do
|
19
|
+
Given { path.stub(:exists?) { true } }
|
20
|
+
Then { loader.exists? path, :provider, 'bar' }
|
21
|
+
|
22
|
+
context "when the extension is read and it matches the naming conventions" do
|
23
|
+
Given { module ::ANamespace; end }
|
24
|
+
Given(:content) { "class ANamespace::BarProvider; def barf; end; end" }
|
25
|
+
When(:provider_class) { loader.read path, :provider, 'bar', ANamespace }
|
26
|
+
Then { path.should have_received(:read).with('providers/bar_provider.rb') }
|
27
|
+
Then { not provider_class.nil? }
|
28
|
+
Then { defined? ::ANamespace::BarProvider }
|
29
|
+
And { provider_class == ::ANamespace::BarProvider }
|
30
|
+
And { provider_class.method_defined? :barf }
|
31
|
+
after do
|
32
|
+
Object.send :remove_const, :ANamespace
|
33
|
+
end
|
34
|
+
end
|
35
|
+
context "when extension does not match naming conventions" do
|
36
|
+
Given(:content) { "class ArgleBargle; end" }
|
37
|
+
When(:result) { loader.read path, :provider, 'bar' }
|
38
|
+
Then { result.should have_failed LoadError }
|
39
|
+
end
|
40
|
+
context "when extension matches naming conventions but is not in the right namespace" do
|
41
|
+
Given { module ::OtherNamespace; end }
|
42
|
+
Given(:content) { "class Object::BarProvider; end" }
|
43
|
+
When(:result) { loader.read path, :provider, 'bar', OtherNamespace }
|
44
|
+
Then { result.should have_failed LoadError }
|
45
|
+
after do
|
46
|
+
Object.send :remove_const, :OtherNamespace
|
47
|
+
end
|
12
48
|
end
|
13
49
|
end
|
14
50
|
end
|
15
|
-
Given(:loader) {MVCLI::Loader.new}
|
16
51
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
52
|
+
describe "a custom extension type" do
|
53
|
+
Given(:handler) { double :TemplateHandler }
|
54
|
+
Given(:extensions) { ({:template => handler}) }
|
55
|
+
Given { handler.stub(:to_path) { |name| "templates/#{name}.txt.erb"} }
|
56
|
+
|
57
|
+
describe "querying" do
|
58
|
+
Then { loader.exists? path, :template, 'servers/show' }
|
59
|
+
And { path.should have_received(:exists?).with 'templates/servers/show.txt.erb' }
|
60
|
+
end
|
61
|
+
describe "loading" do
|
62
|
+
Given(:content) { "Hello Handler"}
|
63
|
+
Given { handler.stub(:define) { |name, bytes| [name, bytes].join ' -> ' } }
|
64
|
+
When(:ext) { loader.read path, :template, 'servers/show' }
|
65
|
+
Then { ext == "servers/show -> Hello Handler" }
|
66
|
+
end
|
22
67
|
end
|
23
68
|
end
|