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.
- 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
|
[](http://badge.fury.io/rb/mvcli)
|
3
3
|
[](https://travis-ci.org/cowboyd/mvcli)
|
4
4
|
[](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
|