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.
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