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
data/lib/mvcli/path.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module MVCLI
|
4
|
+
class Path
|
5
|
+
def initialize(base)
|
6
|
+
@base = Pathname(base.to_s)
|
7
|
+
end
|
8
|
+
|
9
|
+
def exists?(path)
|
10
|
+
@base.join(path).exist?
|
11
|
+
end
|
12
|
+
|
13
|
+
def read(path)
|
14
|
+
@base.join(path).read
|
15
|
+
end
|
16
|
+
|
17
|
+
def join(path)
|
18
|
+
self.class.new @base.join path
|
19
|
+
end
|
20
|
+
|
21
|
+
def nearest(pattern)
|
22
|
+
ancestors.each do |dir|
|
23
|
+
if entry = dir.entries.find { |e| e.to_s.match pattern }
|
24
|
+
return dir.join entry
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def ancestors(dir = @base)
|
30
|
+
if dir == dir.parent
|
31
|
+
[]
|
32
|
+
else
|
33
|
+
[dir] + ancestors(dir.parent)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s(path = nil)
|
38
|
+
path.nil? ? @base.to_s : @base.join(path).to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "mvcli/core"
|
2
|
+
|
3
|
+
module MVCLI
|
4
|
+
class Plugins < MVCLI::Core
|
5
|
+
requires :bundle, :cortex
|
6
|
+
self.path = File.expand_path '../plugins', __FILE__
|
7
|
+
self.namespace = ::MVCLI
|
8
|
+
self.identifier = 'mvcli-plugins'
|
9
|
+
|
10
|
+
def activate!
|
11
|
+
bundle.activate!
|
12
|
+
Core.drain do |cls|
|
13
|
+
core = cls.new if cls.path
|
14
|
+
core.activate!
|
15
|
+
cortex << core
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class MVCLI::PluginsController < MVCLI::Controller
|
2
|
+
requires :cortex, :argv, :bundle
|
3
|
+
|
4
|
+
def install
|
5
|
+
@installation = bundle.replace params[:name], form.attributes
|
6
|
+
respond_with @installation
|
7
|
+
end
|
8
|
+
|
9
|
+
def uninstall
|
10
|
+
@installation = bundle.remove params[:name]
|
11
|
+
respond_with @installation
|
12
|
+
end
|
13
|
+
|
14
|
+
def index
|
15
|
+
@plugins = bundle.plugins
|
16
|
+
respond_with @plugins
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond_with(response, options = {})
|
20
|
+
response
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class MVCLI::Plugins::InstallationModel
|
2
|
+
requires :config
|
3
|
+
|
4
|
+
def initialize(form)
|
5
|
+
@form = form
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
gemspec.name
|
10
|
+
end
|
11
|
+
|
12
|
+
def version
|
13
|
+
gemspec.version
|
14
|
+
end
|
15
|
+
|
16
|
+
def location
|
17
|
+
@form.path
|
18
|
+
end
|
19
|
+
|
20
|
+
def gemspec
|
21
|
+
gemspec = Gem::Specification.load Dir[@form.path.join('*.gemspec')].first
|
22
|
+
config.directory "plugins" do |dir|
|
23
|
+
target = dir.join(gemspec.name)
|
24
|
+
FileUtils.rm_rf target
|
25
|
+
target.make_symlink location
|
26
|
+
Gem.paths.path.unshift dir.to_s
|
27
|
+
request = Gem::RequestSet.new *gemspec.dependencies
|
28
|
+
request.resolve
|
29
|
+
request.install_into dir
|
30
|
+
end
|
31
|
+
return gemspec
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
class MVCLI::BundleProvider
|
2
|
+
requires :config
|
3
|
+
|
4
|
+
def value
|
5
|
+
self
|
6
|
+
end
|
7
|
+
|
8
|
+
def activate!
|
9
|
+
require activatefile if activatefile.exist?
|
10
|
+
end
|
11
|
+
|
12
|
+
def replace(name, options = {})
|
13
|
+
require 'bundler'
|
14
|
+
builder.dependencies.reject! { |dep| dep.name == name }
|
15
|
+
dep, *rest = builder.gem name, options
|
16
|
+
update! name
|
17
|
+
write!
|
18
|
+
return dep
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove(gem_name)
|
22
|
+
require 'bundler'
|
23
|
+
dep = builder.dependencies.find { |d| d.name == gem_name }
|
24
|
+
fail "#{gem_name} is not an installed plugin" unless dep
|
25
|
+
builder.dependencies.reject! { |d| d == dep }
|
26
|
+
update! gem_name
|
27
|
+
write!
|
28
|
+
return dep
|
29
|
+
end
|
30
|
+
|
31
|
+
def plugins
|
32
|
+
lock.specs.select do |spec|
|
33
|
+
builder.dependencies.detect { |dep| dep.name == spec.name }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def gemfile
|
38
|
+
dir.join('Gemfile').tap do |path|
|
39
|
+
unless path.exist?
|
40
|
+
path.open "wb" do |file|
|
41
|
+
file.puts "source 'https://rubygems.org'"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
dir.join 'Gemfile.lock'
|
49
|
+
end
|
50
|
+
|
51
|
+
def setupfile
|
52
|
+
dir.join 'bundle/bundler/setup.rb'
|
53
|
+
end
|
54
|
+
|
55
|
+
def activatefile
|
56
|
+
dir.join 'activate.rb'
|
57
|
+
end
|
58
|
+
|
59
|
+
def dir
|
60
|
+
config.directory('plugins')
|
61
|
+
end
|
62
|
+
|
63
|
+
def builder
|
64
|
+
@builder ||= Bundler::Dsl.new.tap do |builder|
|
65
|
+
builder.eval_gemfile gemfile
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def lock
|
70
|
+
require 'bundler'
|
71
|
+
Bundler::LockfileParser.new lockfile.read
|
72
|
+
end
|
73
|
+
|
74
|
+
def write!
|
75
|
+
gemfile.open "w" do |file|
|
76
|
+
file.puts "source 'https://rubygems.org'"
|
77
|
+
builder.dependencies.each do |dep|
|
78
|
+
file << %|gem "#{dep.name}"|
|
79
|
+
if req = dep.requirements_list.first
|
80
|
+
file << %|, "#{req}"|
|
81
|
+
end
|
82
|
+
options = dep.source ? dep.source.options || {} : {}
|
83
|
+
options = Hash[options.map { |k, v| [k,v.to_s]}]
|
84
|
+
options.merge! "require" => dep.autorequire if dep.autorequire
|
85
|
+
file << %|, #{options.inspect}|
|
86
|
+
file << "\n"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
activatefile.open "w" do |file|
|
90
|
+
file.puts "require #{setupfile.to_s.inspect}"
|
91
|
+
builder.dependencies.each do |dep|
|
92
|
+
require_names = dep.autorequire || [dep.name]
|
93
|
+
require_names.each do |name|
|
94
|
+
file.puts "require #{name.inspect}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def update!(name)
|
101
|
+
path = Bundler.settings[:path]
|
102
|
+
original_definition_method = Bundler.method(:definition)
|
103
|
+
Bundler.with_clean_env do
|
104
|
+
ENV['BUNDLE_GEMFILE'] = gemfile.to_s
|
105
|
+
definition = builder.to_definition(lockfile, name => true)
|
106
|
+
Bundler.settings[:path] = dir.join('bundle').to_s
|
107
|
+
Dir.chdir dir do
|
108
|
+
Bundler.define_singleton_method(:definition) { definition }
|
109
|
+
Bundler::Installer.install dir, definition, standalone: [], "update" => true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
ensure
|
113
|
+
Bundler.define_singleton_method(:definition, &original_definition_method)
|
114
|
+
Bundler.settings[:path] = path
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Installed plugin <%= this.name %> <%= this.requirement %>
|
@@ -0,0 +1 @@
|
|
1
|
+
Uninstalled plugin <%= this.name %>
|
data/lib/mvcli/provisioning.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
require "map"
|
2
|
-
require "active_support/concern"
|
3
|
-
require "active_support/dependencies"
|
4
|
-
require "mvcli/loader"
|
5
2
|
|
6
3
|
module MVCLI
|
7
4
|
module Provisioning
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
UnsatisfiedRequirement = Class.new StandardError
|
10
5
|
MissingScope = Class.new StandardError
|
11
6
|
|
12
|
-
|
7
|
+
def self.included(base)
|
8
|
+
base.send :extend, Requires
|
9
|
+
end
|
10
|
+
|
11
|
+
module Requires
|
13
12
|
def requires(*deps)
|
14
13
|
deps.each do |dep|
|
15
14
|
self.send(:define_method, dep) {Scope[dep]}
|
@@ -17,61 +16,72 @@ module MVCLI
|
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
19
|
+
|
20
|
+
::Object.send :include, self
|
21
|
+
|
20
22
|
class Scope
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
requires :cortex
|
24
|
+
|
25
|
+
def initialize(options = {}, &block)
|
26
|
+
@providers = Map options
|
27
|
+
evaluate &block if block_given?
|
24
28
|
end
|
25
29
|
|
26
30
|
def [](name)
|
27
|
-
|
31
|
+
unless provider = @providers[name]
|
32
|
+
provider = @providers[name] = cortex.read :provider, name
|
33
|
+
end
|
34
|
+
if provider.respond_to?(:value)
|
35
|
+
provider.value
|
36
|
+
elsif provider.respond_to?(:instance_methods) && provider.instance_methods.member?(:value)
|
37
|
+
provider.new.value
|
38
|
+
else
|
39
|
+
provider
|
40
|
+
end
|
28
41
|
end
|
29
42
|
|
30
|
-
def evaluate
|
43
|
+
def evaluate(names = {})
|
31
44
|
old = self.class.current
|
45
|
+
providers = @providers
|
46
|
+
@providers = Map @providers.to_hash.merge(names)
|
32
47
|
self.class.current = self
|
33
48
|
yield
|
34
49
|
ensure
|
50
|
+
@providers = providers
|
35
51
|
self.class.current = old
|
36
52
|
end
|
37
53
|
|
38
|
-
|
39
|
-
Thread.current[self.class.name]
|
40
|
-
end
|
54
|
+
private
|
41
55
|
|
42
|
-
def
|
43
|
-
|
56
|
+
def const(value)
|
57
|
+
Constant.new value
|
44
58
|
end
|
45
59
|
|
46
|
-
|
47
|
-
|
48
|
-
|
60
|
+
class << self
|
61
|
+
def current
|
62
|
+
Thread.current[self.class.name]
|
63
|
+
end
|
49
64
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
65
|
+
def current!
|
66
|
+
current or fail MissingScope, "attempting to access scope, but none is active!"
|
67
|
+
end
|
54
68
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
unless provider = @providers[name]
|
62
|
-
provider = @providers[name] = @loader.load :provider, name
|
69
|
+
def current=(scope)
|
70
|
+
Thread.current[self.class.name] = scope
|
71
|
+
end
|
72
|
+
|
73
|
+
def [](name)
|
74
|
+
current![name]
|
63
75
|
end
|
64
|
-
provider.value
|
65
76
|
end
|
66
|
-
end
|
67
77
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
78
|
+
class Constant
|
79
|
+
attr_reader :value
|
80
|
+
|
81
|
+
def initialize(value)
|
82
|
+
@value = value
|
72
83
|
end
|
73
84
|
end
|
74
85
|
end
|
75
|
-
::Object.send :include, self
|
76
86
|
end
|
77
87
|
end
|
data/lib/mvcli/router.rb
CHANGED
@@ -5,35 +5,52 @@ require "mvcli/argv"
|
|
5
5
|
module MVCLI
|
6
6
|
class Router
|
7
7
|
class RoutingError < StandardError; end
|
8
|
+
requires :actions
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
attr_reader :routes
|
11
|
+
attr_reader :macros
|
12
|
+
|
13
|
+
def initialize
|
11
14
|
@routes = []
|
12
15
|
@macros = []
|
13
16
|
end
|
14
17
|
|
15
|
-
def macro(options)
|
16
|
-
@macros.push Macro.new options
|
17
|
-
end
|
18
|
-
|
19
|
-
def match(options)
|
20
|
-
pattern, action = options.first
|
21
|
-
options.delete pattern
|
22
|
-
@routes << Route.new(pattern, @actions, action, options)
|
23
|
-
end
|
24
|
-
|
25
18
|
def call(command)
|
26
19
|
argv = @macros.reduce(command.argv) do |args, macro|
|
27
20
|
macro.expand args
|
28
21
|
end
|
29
22
|
@routes.each do |route|
|
30
|
-
if
|
31
|
-
return
|
23
|
+
if action = route.match(argv)
|
24
|
+
return action
|
32
25
|
end
|
33
26
|
end
|
34
27
|
fail RoutingError, "no route matches '#{command.argv.join ' '}'"
|
35
28
|
end
|
36
29
|
|
30
|
+
alias_method :[], :call
|
31
|
+
|
32
|
+
class DSL < BasicObject
|
33
|
+
attr_reader :router
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@router = Router.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def macro(options)
|
40
|
+
@router.macros.push Macro.new options
|
41
|
+
end
|
42
|
+
|
43
|
+
def match(options)
|
44
|
+
pattern, action = options.first
|
45
|
+
options.delete pattern
|
46
|
+
@router.routes << Route.new(router, pattern, action, options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def proc(&body)
|
50
|
+
::Proc.new &body
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
37
54
|
class Macro
|
38
55
|
def initialize(options)
|
39
56
|
@pattern, @expansion = options.first
|
@@ -45,19 +62,16 @@ module MVCLI
|
|
45
62
|
end
|
46
63
|
|
47
64
|
class Route
|
48
|
-
def initialize(
|
65
|
+
def initialize(router, pattern, action, options = {})
|
49
66
|
@pattern = Pattern.new pattern.to_s
|
50
|
-
@
|
67
|
+
@router, @action, @options = router, action, options
|
51
68
|
end
|
52
69
|
|
53
70
|
def match(argv)
|
54
71
|
argv = MVCLI::Argv.new argv
|
55
72
|
match = @pattern.match(argv.arguments)
|
56
73
|
if match.matches?
|
57
|
-
|
58
|
-
action = @actions[@action] or fail "no action found for #{@action}"
|
59
|
-
action.call command, match.bindings
|
60
|
-
end
|
74
|
+
@router.actions.new match, @action
|
61
75
|
end
|
62
76
|
end
|
63
77
|
end
|