mvcli 0.0.16 → 0.1.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.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
|