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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f5e10792fd29ac8531f3fad187e62aa0faf29289
4
- data.tar.gz: 4fcf5e6a896261dd9a269e648c5bbba049b37ff3
3
+ metadata.gz: 6e1696b11a3f57cab397bf56d5d6f92256cc2d87
4
+ data.tar.gz: 22ed45da46826cf30cbdbc8872180095c0044c38
5
5
  SHA512:
6
- metadata.gz: 4787013d0c68f4eadb8665f0852f8bad35cf1242073bc39693c5eeb3b9a24102ea502e2f5283f346fc67f350bd44c886f3b1085ebfe8619d289aafb93f26f326
7
- data.tar.gz: a27d207aa5395d024e1ff71c2dc27e692758172b0e3776719ed35f671d987e734560005b13f338245a921d5c8cef74533b55343f911e46e39c0b6714d6fa13f4
6
+ metadata.gz: ca533f2e0da7d76bae735d3e8c7f68e49d53054218c330d3307181f678db4b3269b18d6ab3e8682a51b9581ad4402d9a7efaf61873deabb70e76c00744c637d7
7
+ data.tar.gz: 7d07c41506ab82e1cc4a673dc156cd0db4168c933246e39e6de6c43f67cf67fe8a2cdb363322d67a30318e9a7f24bcc1f34bb82f546e1819c1b5cb77f2695715
@@ -0,0 +1,4 @@
1
+ ## 0.1.0 2014/05/21
2
+
3
+ * make form object implicit to all controllers
4
+
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem "rake"
7
- gem "rspec", "~> 2.13.0"
7
+ gem "rspec", "~> 2.14.0"
8
8
  gem "rspec-given"
9
- gem "rspec-spies"
9
+ gem "aruba"
10
10
  gem "pry"
data/README.md CHANGED
@@ -1,9 +1,13 @@
1
- ## Mvcli
1
+ ## MVCLI
2
2
  [![Gem Version](https://badge.fury.io/rb/mvcli.png)](http://badge.fury.io/rb/mvcli)
3
3
  [![Build Status](https://travis-ci.org/cowboyd/mvcli.png?branch=master)](https://travis-ci.org/cowboyd/mvcli)
4
4
  [![Dependency Status](https://gemnasium.com/cowboyd/mvcli.png)](https://gemnasium.com/cowboyd/mvcli)
5
5
 
6
- This is still an experiment, but I am beginning to suspect it will be nice.
6
+ MVCLI is an first-class application framework for building command
7
+ line applications.
8
+
9
+ If you are familiar with other request-based MVC frameworks like Ruby
10
+ on Rails, then you will feel right at home with mvcli.
7
11
 
8
12
  For an example of an application that uses this, see [rumm][1]
9
13
 
data/app.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "mvcli/app"
2
+
3
+ module MVCLI
4
+ class CLI < ::MVCLI::App
5
+ self.path = File.expand_path '../app', __FILE__
6
+ end
7
+ end
File without changes
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require File.expand_path '../../app', __FILE__
5
+ exit MVCLI::CLI.main
@@ -1,3 +1,4 @@
1
1
  module MVCLI
2
2
  require "mvcli/version"
3
+ require "mvcli/core"
3
4
  end
@@ -0,0 +1,31 @@
1
+ require "map"
2
+
3
+ module MVCLI
4
+ class Action
5
+ requires :cortex, :middleware
6
+
7
+ attr_reader :match, :mapping, :endpoint
8
+
9
+ def initialize(match, mapping)
10
+ @match, @mapping = match, mapping
11
+ end
12
+
13
+ def call(command)
14
+ middleware.call(command) do |command|
15
+ endpoint.call command
16
+ end
17
+ end
18
+
19
+ def endpoint
20
+ return @endpoint if @endpoint
21
+ if mapping.respond_to? :call
22
+ @endpoint = mapping
23
+ else
24
+ require "mvcli/controller"
25
+ controller_name, method = mapping.to_s.split('#')
26
+ controller = cortex.read :controller, controller_name
27
+ @enpoint = controller.new controller_name, method, match.bindings
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,41 +1,36 @@
1
- require "mvcli"
2
- require "mvcli/middleware/exit_status"
3
- require "mvcli/middleware/exception_logger"
4
- require_relative "middleware"
5
- require_relative "command"
6
- require_relative "actions"
7
- require_relative "router"
8
- require_relative "provisioning"
1
+ require "mvcli/cortex"
2
+ require "mvcli/loader"
3
+ require "mvcli/action"
4
+ require "mvcli/command"
9
5
 
10
6
  module MVCLI
11
- class App
12
- def initialize
13
- @router = Router.new Actions.new root
14
- @router.instance_eval route_file.read, route_file.to_s, 1
15
- [:providers, :controllers, :forms, :models].each do |path|
16
- ActiveSupport::Dependencies.autoload_paths << root.join('app', path.to_s)
17
- end
18
- @middleware = Middleware.new
19
- @middleware << MVCLI::Middleware::ExitStatus.new
20
- @middleware << MVCLI::Middleware::ExceptionLogger.new
21
- @middleware << Provisioning::Middleware.new
22
- @middleware << @router
23
- end
7
+ class App < MVCLI::Core
8
+ requires :router
24
9
 
25
10
  def call(command)
26
- @middleware.call command
11
+ Scope.new(bootstrap command) do
12
+ cortex.activate!
13
+ action = router[command]
14
+ return action.call command
15
+ end
27
16
  end
28
17
 
29
- def root
30
- self.class.root or fail "Invalid App: undefined application root directory"
18
+ def bootstrap(command)
19
+ Map command: command, app: self, cortex: cortex, loader: loader, actions: Action
31
20
  end
32
21
 
33
- def route_file
34
- root.join 'app/routes.rb'
22
+ def cortex
23
+ @cortex ||= Cortex.new do |cortex|
24
+ Core.drain do |cls|
25
+ cortex << cls.new if cls.path
26
+ end
27
+ end
35
28
  end
36
29
 
37
- class << self
38
- attr_accessor :root
30
+ def loader
31
+ #TODO: use generic extension mechanism
32
+ require "mvcli/std/extensions/erb_extension"
33
+ MVCLI::Loader.new :template => MVCLI::ERBExtension.new
39
34
  end
40
35
 
41
36
  def main(argv = ARGV.dup, input = $stdin, output = $stdout, log = $stderr, env = ENV.dup)
@@ -1,7 +1,25 @@
1
+ require "mvcli/erb"
2
+ require "mvcli/form"
3
+
1
4
  class MVCLI::Controller
5
+ requires :command, :cortex, :argv
2
6
  attr_reader :params
3
7
 
4
- def initialize(params = {})
5
- @params = params
8
+ def initialize(name, method, params)
9
+ @name, @method, @params = name, method, params
10
+ end
11
+
12
+ def call(command)
13
+ response = send @method
14
+ template = cortex.read :template, "#{@name}/#{@method}"
15
+ template.call response, command.output
16
+ return 0
17
+ end
18
+
19
+ def form
20
+ template = cortex.read :form, "#{@name}/#{@method}"
21
+ form = template.new argv.options
22
+ form.validate!
23
+ form
6
24
  end
7
25
  end
@@ -0,0 +1,101 @@
1
+ require "map"
2
+ require "mvcli/path"
3
+ require "mvcli/provisioning"
4
+
5
+ module MVCLI
6
+ class ExtensionNotFound < StandardError; end
7
+ class InvalidPath < StandardError; end
8
+
9
+ class Core
10
+
11
+ requires :loader
12
+ attr_accessor :path, :name, :namespace
13
+
14
+ def initialize(options = {})
15
+ options = Map options
16
+ @path = options[:path]
17
+ @name = options[:name]
18
+ @namespace = options[:namespace]
19
+ end
20
+
21
+ def activate!
22
+ end
23
+
24
+ def path
25
+ path = @path || self.class.path
26
+ fail InvalidPath, "core `#{name}` cannot have a nil path" unless path
27
+ path.is_a?(MVCLI::Path) ? path : MVCLI::Path.new(path.to_s)
28
+ end
29
+
30
+ def namespace
31
+ @namespace || self.class.namespace || enclosing_namespace || Object
32
+ end
33
+
34
+ def name
35
+ @name || self.class.identifier || self.class.name.gsub('::', '-').downcase unless namespace == Object
36
+ end
37
+
38
+ def version
39
+ spec = Gem::Specification.load path.nearest('.gemspec$').to_s
40
+ spec.version
41
+ end
42
+
43
+ def exists?(extension_type, name)
44
+ loader.exists? path, extension_type, name
45
+ end
46
+
47
+ def read(extension_type, name, options = {})
48
+ unless exists? extension_type, name
49
+ fail ExtensionNotFound, "unable to locate #{extension_type} '#{name}'"
50
+ end
51
+ this = self
52
+ loader.read(path, extension_type, name, namespace).tap do |ext|
53
+ [ext, ext.singleton_class].each do |cls|
54
+ cls.send(:define_method, :core) { this } if cls.respond_to?(:define_method, true)
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def enclosing_namespace
62
+ if self.class != MVCLI::Core && name = self.class.name
63
+ components = name.split('::')[0..-2]
64
+ unless components.empty?
65
+ eval components.join('::')
66
+ end
67
+ end
68
+ end
69
+
70
+ class << self
71
+ include Enumerable
72
+ attr_accessor :path, :identifier, :namespace, :version
73
+
74
+ def inherited(base)
75
+ ::MVCLI::Core << base
76
+ end
77
+
78
+ def all
79
+ @all ||= []
80
+ end
81
+
82
+ def <<(base)
83
+ all << base
84
+ end
85
+
86
+ def each(&visitor)
87
+ all.each &visitor
88
+ end
89
+
90
+ def drain(&visitor)
91
+ each &visitor
92
+ all.clear
93
+ end
94
+ end
95
+
96
+ class Std < self
97
+ self.path = Pathname(__FILE__).dirname.join 'std'
98
+ self.namespace = MVCLI
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,38 @@
1
+ require "mvcli/core"
2
+
3
+ module MVCLI
4
+ class Cortex
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @cores = []
9
+ yield self if block_given?
10
+ end
11
+
12
+ def activate!
13
+ each(&:activate!)
14
+ end
15
+
16
+ def <<(core)
17
+ tap do
18
+ @cores << core
19
+ end
20
+ end
21
+
22
+ def each(&block)
23
+ @cores.each &block
24
+ end
25
+
26
+ def exists?(extension_point, extension_name)
27
+ @cores.detect {|core| core.exists? extension_point, extension_name }
28
+ end
29
+
30
+ def read(extension_point, extension_name)
31
+ if core = exists?(extension_point, extension_name)
32
+ core.read extension_point, extension_name
33
+ else
34
+ fail MVCLI::ExtensionNotFound, "unable to find #{extension_point} '#{extension_name}'"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,6 +1,19 @@
1
1
  require "mvcli/decoding"
2
2
  require "mvcli/validatable"
3
3
 
4
+ #TODO: need a way to leverage actual input inside error messages
5
+ # validates(:path, 'no such directory #{path}' ) { path.exist? }
6
+
7
+ #TODO: need a way to nest validation in order to specify dependencies. e.g.
8
+ # validates(:path) { path.exist? }.then do
9
+ # validates(:path) { path.read_first_line == 'BEGIN' }
10
+ # end
11
+
12
+
13
+ #TODO: need a way to validate multiple values. e.g.
14
+ # validates(:address, :port) { |a, p| address_valid_for_port a, p }
15
+
16
+
4
17
  module MVCLI
5
18
  class Form
6
19
  include MVCLI::Validatable
@@ -2,10 +2,54 @@ require "active_support/inflector/methods"
2
2
 
3
3
  module MVCLI
4
4
  class Loader
5
- include ActiveSupport::Inflector
5
+
6
+ def initialize(extensions = {})
7
+ @extensions = Map extensions
8
+ @default_handler = RubyClassLoader.new
9
+ end
6
10
 
7
11
  def load(type, name, *args, &block)
8
12
  constantize(camelize("#{name}_#{type}")).new(*args, &block)
9
13
  end
14
+
15
+ def exists?(path, extension_type, name)
16
+ pathname = handler(extension_type).to_path name, extension_type
17
+ path.exists? pathname
18
+ end
19
+
20
+ def read(path, extension_type, name, namespace = Object)
21
+ pathname = handler(extension_type).to_path name, extension_type
22
+ bytes = path.read pathname
23
+ handler(extension_type).define name, bytes, extension_type, namespace
24
+ end
25
+
26
+ private
27
+
28
+ def handler(extension_type)
29
+ @extensions[extension_type] || @default_handler
30
+ end
31
+
32
+ class RubyClassLoader
33
+ include ActiveSupport::Inflector
34
+
35
+ def to_path(name, extension_type)
36
+ "#{pluralize extension_type}/#{name}_#{extension_type}.rb"
37
+ end
38
+
39
+ def define(name, bytes, extension_type, namespace)
40
+ eval bytes, TOPLEVEL_BINDING, to_path(name, extension_type), 1
41
+ components = [namespace.name, classify("#{name}_#{extension_type}")]
42
+ components.shift if namespace == Object
43
+ lookup components.join('::'), to_path(name, extension_type)
44
+ end
45
+
46
+ private
47
+
48
+ def lookup(class_name, filename)
49
+ constantize class_name
50
+ rescue NameError
51
+ fail LoadError, "expected #{filename} to define #{class_name}"
52
+ end
53
+ end
10
54
  end
11
55
  end
@@ -2,10 +2,19 @@ module MVCLI
2
2
  class Middleware
3
3
  def initialize
4
4
  @apps = []
5
+ yield self if block_given?
5
6
  end
6
7
 
7
- def call(command)
8
- invoke command, 0
8
+ def call(command, apps = @apps, &block)
9
+ app, *rest = apps + [block].compact
10
+ if app
11
+ app.call(command) do |yielded|
12
+ yielded ||= command
13
+ call yielded, rest
14
+ end
15
+ else
16
+ return 0
17
+ end
9
18
  end
10
19
 
11
20
  def [](idx)
@@ -20,19 +29,8 @@ module MVCLI
20
29
  @apps << app
21
30
  end
22
31
 
23
- private
24
-
25
- def invoke(command, index)
26
- if app = @apps[index]
27
- app.call(command) do |c|
28
- if @apps[index + 1]
29
- c ||= command
30
- invoke c, index + 1
31
- end
32
- end
33
- else
34
- return 0
35
- end
32
+ def length
33
+ @apps.length
36
34
  end
37
35
  end
38
36
  end