mvcli 0.0.16 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e1696b11a3f57cab397bf56d5d6f92256cc2d87
|
4
|
+
data.tar.gz: 22ed45da46826cf30cbdbc8872180095c0044c38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca533f2e0da7d76bae735d3e8c7f68e49d53054218c330d3307181f678db4b3269b18d6ab3e8682a51b9581ad4402d9a7efaf61873deabb70e76c00744c637d7
|
7
|
+
data.tar.gz: 7d07c41506ab82e1cc4a673dc156cd0db4168c933246e39e6de6c43f67cf67fe8a2cdb363322d67a30318e9a7f24bcc1f34bb82f546e1819c1b5cb77f2695715
|
data/Changelog.md
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
##
|
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
|
-
|
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
data/app/routes.rb
ADDED
File without changes
|
data/bin/mvcli
ADDED
data/lib/mvcli.rb
CHANGED
data/lib/mvcli/action.rb
ADDED
@@ -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
|
data/lib/mvcli/app.rb
CHANGED
@@ -1,41 +1,36 @@
|
|
1
|
-
require "mvcli"
|
2
|
-
require "mvcli/
|
3
|
-
require "mvcli/
|
4
|
-
|
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
|
-
|
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
|
-
|
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
|
30
|
-
|
18
|
+
def bootstrap(command)
|
19
|
+
Map command: command, app: self, cortex: cortex, loader: loader, actions: Action
|
31
20
|
end
|
32
21
|
|
33
|
-
def
|
34
|
-
|
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
|
-
|
38
|
-
|
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)
|
data/lib/mvcli/controller.rb
CHANGED
@@ -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(
|
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
|
data/lib/mvcli/core.rb
ADDED
@@ -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
|
data/lib/mvcli/cortex.rb
ADDED
@@ -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
|
data/lib/mvcli/form.rb
CHANGED
@@ -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
|
data/lib/mvcli/loader.rb
CHANGED
@@ -2,10 +2,54 @@ require "active_support/inflector/methods"
|
|
2
2
|
|
3
3
|
module MVCLI
|
4
4
|
class Loader
|
5
|
-
|
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
|
data/lib/mvcli/middleware.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|