mvcli 0.0.16 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +4 -0
  3. data/Gemfile +2 -2
  4. data/README.md +6 -2
  5. data/app.rb +7 -0
  6. data/app/routes.rb +0 -0
  7. data/bin/mvcli +5 -0
  8. data/lib/mvcli.rb +1 -0
  9. data/lib/mvcli/action.rb +31 -0
  10. data/lib/mvcli/app.rb +23 -28
  11. data/lib/mvcli/controller.rb +20 -2
  12. data/lib/mvcli/core.rb +101 -0
  13. data/lib/mvcli/cortex.rb +38 -0
  14. data/lib/mvcli/form.rb +13 -0
  15. data/lib/mvcli/loader.rb +45 -1
  16. data/lib/mvcli/middleware.rb +13 -15
  17. data/lib/mvcli/path.rb +41 -0
  18. data/lib/mvcli/plugins.rb +19 -0
  19. data/lib/mvcli/plugins/controllers/plugins_controller.rb +23 -0
  20. data/lib/mvcli/plugins/forms/plugins/install_form.rb +6 -0
  21. data/lib/mvcli/plugins/models/plugins/installation_model.rb +33 -0
  22. data/lib/mvcli/plugins/providers/bundle_provider.rb +116 -0
  23. data/lib/mvcli/plugins/routes.rb +3 -0
  24. data/lib/mvcli/plugins/templates/plugins/index.txt.erb +3 -0
  25. data/lib/mvcli/plugins/templates/plugins/install.txt.erb +1 -0
  26. data/lib/mvcli/plugins/templates/plugins/uninstall.txt.erb +1 -0
  27. data/lib/mvcli/provisioning.rb +48 -38
  28. data/lib/mvcli/router.rb +34 -20
  29. data/lib/mvcli/std/extensions/erb_extension.rb +24 -0
  30. data/lib/mvcli/std/providers/argv_provider.rb +9 -0
  31. data/lib/mvcli/std/providers/config_provider.rb +29 -0
  32. data/lib/mvcli/std/providers/middleware_provider.rb +12 -0
  33. data/lib/mvcli/std/providers/router_provider.rb +17 -0
  34. data/lib/mvcli/std/routes.rb +2 -0
  35. data/lib/mvcli/version.rb +1 -1
  36. data/spec/features/managing_plugins_spec.rb +29 -0
  37. data/spec/fixtures/apps/trivium/app.rb +10 -0
  38. data/spec/fixtures/apps/trivium/app/routes.rb +0 -0
  39. data/spec/fixtures/apps/trivium/bin/trivium +6 -0
  40. data/spec/fixtures/apps/trivium/lib/trivium/version.rb +3 -0
  41. data/spec/fixtures/apps/trivium/trivium.gemspec +8 -0
  42. data/spec/fixtures/bin/trivium +3 -0
  43. data/spec/fixtures/plugins/timing-plugin/app/routes.rb +1 -0
  44. data/spec/fixtures/plugins/timing-plugin/lib/trivium-timing.rb +5 -0
  45. data/spec/fixtures/plugins/timing-plugin/trivium-timing.gemspec +9 -0
  46. data/spec/mvcli/action_spec.rb +31 -0
  47. data/spec/mvcli/controller_spec.rb +35 -0
  48. data/spec/mvcli/core_spec.rb +77 -0
  49. data/spec/mvcli/cortex_spec.rb +50 -0
  50. data/spec/mvcli/erb_spec.rb +1 -1
  51. data/spec/mvcli/form_spec.rb +1 -1
  52. data/spec/mvcli/loader_spec.rb +58 -13
  53. data/spec/mvcli/middleware/exception_logger_spec.rb +3 -3
  54. data/spec/mvcli/middleware/exit_status_spec.rb +10 -10
  55. data/spec/mvcli/middleware_spec.rb +28 -45
  56. data/spec/mvcli/path_spec.rb +13 -0
  57. data/spec/mvcli/path_spec/does/exist +1 -0
  58. data/spec/mvcli/plugins/providers/bundle_provider_spec.rb +23 -0
  59. data/spec/mvcli/provisioning_spec.rb +39 -37
  60. data/spec/mvcli/router_spec.rb +26 -32
  61. data/spec/mvcli/std/extensions/erb_extension_spec.rb +34 -0
  62. data/spec/mvcli/std/providers/middleware_provider_spec.rb +10 -0
  63. data/spec/mvcli/validatable_spec.rb +12 -12
  64. data/spec/spec_helper.rb +13 -1
  65. data/spec/support/aruba_helper.rb +76 -0
  66. metadata +69 -33
  67. data/lib/mvcli/actions.rb +0 -37
  68. data/lib/mvcli/renderer.rb +0 -16
  69. data/spec/mvcli/actions_spec.rb +0 -34
  70. 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,9 @@
1
+ require "mvcli/argv"
2
+
3
+ class MVCLI::ArgvProvider
4
+ requires :command
5
+
6
+ def value
7
+ MVCLI::Argv.new command.argv
8
+ end
9
+ 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
@@ -0,0 +1,2 @@
1
+ macro %r{^(-v|--version)} => "version"
2
+ match 'version' => proc { |cmd| cmd.output.puts ::MVCLI::Provisioning::Scope[:app].version }
@@ -1,3 +1,3 @@
1
1
  module MVCLI
2
- VERSION = "0.0.16"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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
@@ -0,0 +1,10 @@
1
+ require "mvcli/app"
2
+ require "mvcli/plugins"
3
+ require "trivium/version"
4
+
5
+ module Trivium
6
+ class App < MVCLI::App
7
+ self.path = Pathname(__FILE__).dirname.join('app')
8
+ self.identifier = 'trivium'
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ $:.unshift File.expand_path '../../lib', __FILE__
5
+ require File.expand_path '../../app', __FILE__
6
+ exit Trivium::App.main
@@ -0,0 +1,3 @@
1
+ module Trivium
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,8 @@
1
+ # -*- mode: ruby -*-
2
+ # encoding: utf-8
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "trivium"
6
+ spec.version = "1.0.0"
7
+ spec.require_paths = ['lib']
8
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- ruby -*-
3
+ load File.expand_path '../../apps/trivium/bin/trivium', __FILE__
@@ -0,0 +1 @@
1
+ match 'time' => proc { |cmd| cmd.output.puts '1s' }
@@ -0,0 +1,5 @@
1
+ module Trivium
2
+ class Timing < MVCLI::Core
3
+ self.path = Pathname(__FILE__).join '../../app'
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ # -*- mode: ruby -*-
2
+ # encoding: utf-8
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "trivium-timing"
6
+ spec.version = "1.0.0"
7
+ spec.files = [__FILE__, 'app/routes.rb', 'lib/trivium-timing']
8
+ spec.require_paths = ['lib']
9
+ end
@@ -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
@@ -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 mock(:Context, :name => 'Charles'), output}
10
+ When {template.call double(:Context, :name => 'Charles'), output}
11
11
  Then {output.should eql "name: Charles"}
12
12
  end
13
13
  end
@@ -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) {mock(:NameGenerator, generate: 'random-name')}
34
+ f.stub(:naming) {double(:NameGenerator, generate: 'random-name')}
35
35
  end
36
36
  end
37
37
  context "with invalid array properties" do
@@ -2,22 +2,67 @@ require "spec_helper"
2
2
  require "mvcli/loader"
3
3
 
4
4
  describe "MVCLI::Loader" do
5
- Given do
6
- class ::TotallyAwesomeController
7
- attr_reader :params
8
- attr_reader :block
9
- def initialize(params, &block)
10
- @params = params
11
- @block = block
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
- context "when a controller load is requested in the global namespace" do
18
- When(:controller){loader.load(:controller, 'totally_awesome', {:foo => :bar}) {'whee!'}}
19
- Then {controller.should be_instance_of TotallyAwesomeController}
20
- And {controller.params.should eql ({:foo => :bar})}
21
- And {controller.block.call.should eql "whee!"}
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