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