rpv_core 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +3 -0
- data/bin/rpv +68 -0
- data/lib/rpv/autoload.rb +36 -0
- data/lib/rpv/base_presenter.rb +41 -0
- data/lib/rpv/event_publisher.rb +125 -0
- data/lib/rpv/generators/app_generator.rb +17 -0
- data/lib/rpv/generators/base.rb +130 -0
- data/lib/rpv/generators/presenter_generator.rb +28 -0
- data/lib/rpv/generators/templates/app/README +12 -0
- data/lib/rpv/generators/templates/app/Rakefile +3 -0
- data/lib/rpv/generators/templates/app/app/main.rb +4 -0
- data/lib/rpv/generators/templates/app/app/setup.rb +11 -0
- data/lib/rpv/generators/templates/app/spec/spec_helper.rb +5 -0
- data/lib/rpv/generators/templates/app/tasks/rspec.rake +9 -0
- data/lib/rpv/generators/templates/presenter/presenter.rb.erb +10 -0
- data/lib/rpv/generators/templates/presenter/presenter_spec.rb.erb +22 -0
- data/lib/rpv/inflector.rb +68 -0
- data/lib/rpv/method.rb +13 -0
- data/lib/rpv/method_helper.rb +9 -0
- data/lib/rpv/option_extractor.rb +15 -0
- data/lib/rpv/spec_helper.rb +118 -0
- data/lib/rpv/wiring/base.rb +49 -0
- data/lib/rpv/wiring/callable.rb +20 -0
- data/lib/rpv/wiring/converting.rb +36 -0
- data/lib/rpv/wiring/helpers.rb +23 -0
- data/lib/rpv/wiring/simple.rb +19 -0
- data/lib/rpv/wiring/unidirectional.rb +38 -0
- data/lib/rpv/wiring.rb +4 -0
- data/lib/rpv.rb +20 -0
- data/spec/autoload_spec.rb +24 -0
- data/spec/base_presenter_spec.rb +16 -0
- data/spec/event_publisher_spec.rb +121 -0
- data/spec/files/autoload/b/some_module.rb +2 -0
- data/spec/files/autoload/some_class.rb +3 -0
- data/spec/files/bar/presenter.rb.erb +5 -0
- data/spec/files/bar/view.rb.erb +3 -0
- data/spec/files/foo/Rakefile +0 -0
- data/spec/files/foo/app/main.rb.erb +1 -0
- data/spec/files/foo/app/models/templates/lala.html.erb +2 -0
- data/spec/generators/base_spec.rb +81 -0
- data/spec/generators/presenter_spec.rb +27 -0
- data/spec/method_helper_spec.rb +19 -0
- data/spec/method_spec.rb +12 -0
- data/spec/option_extractor_spec.rb +22 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/spec_helper_spec.rb +15 -0
- data/spec/wiring/base_spec.rb +17 -0
- data/spec/wiring/callable_spec.rb +50 -0
- data/spec/wiring/helpers_spec.rb +29 -0
- data/spec/wiring/simple_spec.rb +47 -0
- data/spec/wiring/unidirectional_spec.rb +46 -0
- data/tasks/rdoc.rake +10 -0
- data/tasks/rspec.rake +10 -0
- metadata +118 -0
data/Rakefile
ADDED
data/bin/rpv
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#TODO ^
|
3
|
+
|
4
|
+
require 'fileutils'
|
5
|
+
require 'rpv/inflector'
|
6
|
+
|
7
|
+
def usage
|
8
|
+
puts <<-END
|
9
|
+
rpv new APP_NAME
|
10
|
+
#creates a new app in ./APP_NAME
|
11
|
+
rpv g GENERATOR ARG:VALUE
|
12
|
+
#invokes a generator
|
13
|
+
#call without argument to see what generators are available
|
14
|
+
#This only works when run in an application's directory
|
15
|
+
END
|
16
|
+
exit(0)
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate
|
20
|
+
$LOAD_PATH << FileUtils.pwd
|
21
|
+
begin
|
22
|
+
require 'app/setup'
|
23
|
+
RPV::Generators.require_all
|
24
|
+
rescue LoadError
|
25
|
+
puts "Could not load ./app/setup.rb; make sure pwd is the application's dir"
|
26
|
+
exit(0)
|
27
|
+
end
|
28
|
+
if ARGV[1].nil?
|
29
|
+
RPV::Generators.all.each do |c|
|
30
|
+
puts " " + c.to_s.gsub("RPV::Generators::","").underscore.gsub("_generator", "") + ' #' + c.describe
|
31
|
+
puts " args: " + c.argument_definitions.join(", ")
|
32
|
+
end
|
33
|
+
else
|
34
|
+
args = ARGV.dup
|
35
|
+
args.delete_at(0)
|
36
|
+
generator_class = ("RPV::Generators::" + (args.delete_at(0) + "_generator").camelize).constantize
|
37
|
+
generator_args = {}
|
38
|
+
args.each do |a|
|
39
|
+
if a =~ /(.*):(.*)/
|
40
|
+
generator_args[$1.to_sym] = $2
|
41
|
+
end
|
42
|
+
end
|
43
|
+
generator_class.new.execute *[generator_args.merge({:to_path => '.'})]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
case ARGV[0]
|
50
|
+
when 'new'
|
51
|
+
usage if ARGV[1].nil?
|
52
|
+
require 'rpv/generators/app_generator'
|
53
|
+
RPV::Generators::AppGenerator.new.execute :to_path => "./#{ARGV[1]}"
|
54
|
+
when 'g' #generate
|
55
|
+
generate
|
56
|
+
when 'r' #run
|
57
|
+
$LOAD_PATH << FileUtils.pwd
|
58
|
+
begin
|
59
|
+
require 'app/main'
|
60
|
+
rescue LoadError
|
61
|
+
puts "Could not load ./app/main.rb; make sure pwd is the application's dir"
|
62
|
+
exit(0)
|
63
|
+
end
|
64
|
+
else nil
|
65
|
+
usage
|
66
|
+
end
|
67
|
+
|
68
|
+
|
data/lib/rpv/autoload.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rpv/inflector'
|
2
|
+
|
3
|
+
module RPV
|
4
|
+
module Autoload
|
5
|
+
def self.setup_paths(*paths)
|
6
|
+
paths = [paths].flatten.map { |p| File.expand_path(p) }
|
7
|
+
add_paths = paths.dup
|
8
|
+
paths.each do |base|
|
9
|
+
#trouble with directory named 'lib' inside jar, seems
|
10
|
+
#like it was not found by Dir[]
|
11
|
+
#puts "--- #{base}"
|
12
|
+
#puts Dir["#{base}/lib/**/*"]
|
13
|
+
Dir["#{base}/**/*"].each do |path|
|
14
|
+
if File.directory?(path)
|
15
|
+
add_paths << path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
add_paths.each do |path|
|
20
|
+
$LOAD_PATH << path unless $LOAD_PATH.include?(path)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#overwrite
|
27
|
+
class Class
|
28
|
+
def const_missing(sym)
|
29
|
+
begin
|
30
|
+
require sym.to_s.underscore
|
31
|
+
rescue LoadError => e
|
32
|
+
raise NameError.new("Missing constant #{sym.to_s} (#{e.to_s})")
|
33
|
+
end
|
34
|
+
sym.to_s.constantize
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rpv/wiring'
|
2
|
+
require 'rpv/method_helper'
|
3
|
+
require 'rpv/option_extractor'
|
4
|
+
|
5
|
+
module RPV
|
6
|
+
class BasePresenter
|
7
|
+
include MethodHelper
|
8
|
+
include OptionExtractor
|
9
|
+
|
10
|
+
attr_reader :model, :view
|
11
|
+
|
12
|
+
def self.wires(w = nil)
|
13
|
+
@wires = (@wires || []) << w unless w.nil?
|
14
|
+
@wires || []
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_view(args = {})
|
22
|
+
update(:view, args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_model(args = {})
|
26
|
+
update(:model, args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_presenter(args = {})
|
30
|
+
update(:presenter, args)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def update(what, args = {})
|
35
|
+
what = what.to_s
|
36
|
+
self.class.wires.select { |w| w.send("#{what}_match", args) }.each do |w|
|
37
|
+
w.send("update_#{what}", @model, @view, self)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module RPV
|
2
|
+
# This module is used to publish events.
|
3
|
+
# Usage:
|
4
|
+
# class Foo < SomeThing
|
5
|
+
# include RPV::EventPublisher
|
6
|
+
#
|
7
|
+
# fires :some_event
|
8
|
+
# def bar
|
9
|
+
# fire :some_event
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Now you can subscribe to the event:
|
14
|
+
# foo = Foo.new
|
15
|
+
# foo.subscribe :some_event do
|
16
|
+
# #do something
|
17
|
+
# end
|
18
|
+
# Or:
|
19
|
+
# foo.subscribe :some_event, RPV::Method.new(self, :some_method)
|
20
|
+
# Or (if you included RPV::MethodHelper):
|
21
|
+
# foo.subscribe :some_event, method(:some_method)
|
22
|
+
module EventPublisher
|
23
|
+
def self.included(base) #:nodoc:
|
24
|
+
base.extend ClassMethods
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
# Declare events.
|
29
|
+
# fires :some_event
|
30
|
+
# You can also declare several events at a time.
|
31
|
+
# fires :event_a, :event_b, :event_c
|
32
|
+
def fires(*events)
|
33
|
+
#TODO make inheritable?
|
34
|
+
@events ||= []
|
35
|
+
@events += [events].flatten.map { |e| e.to_sym }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Declare aliases for events.
|
39
|
+
# alias_event :some_event, :whatever
|
40
|
+
# Event receivers now can subscribe to the event :whatever.
|
41
|
+
# It's also possible to assign the same alias to multiple events.
|
42
|
+
# Aliases can only be used to subscribe to.
|
43
|
+
def alias_event(event, event_alias)
|
44
|
+
@event_aliases ||= Hash.new{|h,k| h[k] = []}
|
45
|
+
@event_aliases[event_alias.to_sym] << event.to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
# This method accepts regular expressions for aliasing events.
|
49
|
+
# Given there are following events:
|
50
|
+
# - ok_action_performed
|
51
|
+
# - cancel_action_performed
|
52
|
+
# alias_events /^(.*)_action_performed$/, "$1_clicked"
|
53
|
+
# Produces the aliases "ok_clicked" and "cancel_clicked"
|
54
|
+
# It can also be used to
|
55
|
+
# alias_events /^(.*)_action_performed$/, "some_button_clicked"
|
56
|
+
def alias_events(regex, alias_rule)
|
57
|
+
@events ||= []
|
58
|
+
@events.each do |event|
|
59
|
+
if match = regex.match(event.to_s)
|
60
|
+
alias_event event, alias_rule.gsub(/\$([0-9]+)/).each { |e| match[e[1..-1].to_i] }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns all registered events.
|
66
|
+
def events
|
67
|
+
@events || []
|
68
|
+
end
|
69
|
+
|
70
|
+
def event_aliases
|
71
|
+
@event_aliases || Hash.new{|h,k| h[k] = []}
|
72
|
+
end
|
73
|
+
|
74
|
+
def fires?(event)
|
75
|
+
events.include?(event) || event_aliases.keys.include?(event)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns all registered events.
|
80
|
+
def events
|
81
|
+
self.class.events
|
82
|
+
end
|
83
|
+
|
84
|
+
def event_aliases
|
85
|
+
self.class.event_aliases
|
86
|
+
end
|
87
|
+
|
88
|
+
# Subscribe to an event.
|
89
|
+
# method should be a RPV::Method or some object that responds to call(*args)
|
90
|
+
def subscribe(event, method = nil, &block)
|
91
|
+
@subscriptions ||= Hash.new{|h,k| h[k] = []}
|
92
|
+
if event.is_a?(String) || event.is_a?(Symbol)
|
93
|
+
if events.include? event
|
94
|
+
unless method.nil?
|
95
|
+
@subscriptions[event] << { :receiver => method }
|
96
|
+
else
|
97
|
+
@subscriptions[event] << { :receiver => block }
|
98
|
+
end
|
99
|
+
elsif event_aliases.keys.include? event
|
100
|
+
event_aliases[event].each do |event|
|
101
|
+
subscribe event, method, &block
|
102
|
+
end
|
103
|
+
end
|
104
|
+
elsif event.is_a?(Array)
|
105
|
+
event.uniq.each do |e|
|
106
|
+
subscribe(e, method, &block)
|
107
|
+
end
|
108
|
+
elsif event.is_a?(Regexp)
|
109
|
+
subscribe(events.find_all { |e| e.to_s =~ event }, method, &block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
alias :when :subscribe
|
114
|
+
|
115
|
+
private
|
116
|
+
def fire(event, *args)
|
117
|
+
@subscriptions ||= Hash.new{|h,k| h[k] = []}
|
118
|
+
if @subscriptions.has_key? event
|
119
|
+
@subscriptions[event].each do |subscription|
|
120
|
+
subscription[:receiver].call *args
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rpv/generators/base'
|
2
|
+
|
3
|
+
module RPV
|
4
|
+
module Generators
|
5
|
+
class AppGenerator < Base
|
6
|
+
copy_all
|
7
|
+
|
8
|
+
create_dir 'app/presenters'
|
9
|
+
create_dir 'app/views'
|
10
|
+
create_dir 'app/models'
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@template_path = File.join(File.dirname(__FILE__), 'templates/app')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'rpv/inflector'
|
4
|
+
|
5
|
+
module RPV
|
6
|
+
module Generators
|
7
|
+
class Base
|
8
|
+
def self.describe
|
9
|
+
""
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.argument(name)
|
13
|
+
@argument_definitions ||= []
|
14
|
+
@argument_definitions << name
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.argument_definitions
|
18
|
+
@argument_definitions || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.copy_all
|
22
|
+
copy '**/*'
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.copy(glob = nil)
|
26
|
+
@copy = (@copy || []) << glob unless glob.nil?
|
27
|
+
@copy || []
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds a directory that should be created, although it is empty.
|
31
|
+
# Actually the directory does not need to exist in the template.
|
32
|
+
# Expects a relative path.
|
33
|
+
def self.create_dir(rel_path = nil)
|
34
|
+
@create_dir = (@create_dir || []) << rel_path unless rel_path.nil?
|
35
|
+
@create_dir || []
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def self.parse(rel_path, new_path = nil)
|
40
|
+
@templates ||= {}
|
41
|
+
@templates[rel_path] = new_path || rel_path.gsub(/\.erb$/, '')
|
42
|
+
copy rel_path
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.templates
|
46
|
+
@templates || {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute(args = {})
|
50
|
+
to_path = File.join(FileUtils.pwd, args[:to_path]) #TODO
|
51
|
+
FileUtils.mkdir_p to_path
|
52
|
+
@arguments = args
|
53
|
+
parse_args
|
54
|
+
|
55
|
+
files = self.class.copy.map { |c| Dir.glob(File.join(@template_path, c)) }.flatten.uniq
|
56
|
+
|
57
|
+
files_new_path = {}
|
58
|
+
files.each do |file|
|
59
|
+
rel_path = relative_path(file, @template_path)
|
60
|
+
if self.class.templates.keys.member? rel_path
|
61
|
+
new_rel_path = eval("\"#{self.class.templates[rel_path]}\"")
|
62
|
+
files_new_path[File.join(to_path, new_rel_path)] = file
|
63
|
+
else
|
64
|
+
files_new_path[File.join(to_path, rel_path)] = file
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
files_new_path.keys.sort_by { |p| p.count('/') }.each do |new_path|
|
69
|
+
file = files_new_path[new_path]
|
70
|
+
rel_path = relative_path(new_path, to_path)
|
71
|
+
if File.directory?(file)
|
72
|
+
if File.exists?(new_path)
|
73
|
+
puts "exists #{rel_path}"
|
74
|
+
else
|
75
|
+
FileUtils.mkdir_p new_path
|
76
|
+
puts "create #{rel_path}"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
exists = File.exists?(new_path)
|
80
|
+
if !exists || (exists && overwrite?(rel_path))
|
81
|
+
puts "create #{rel_path}"
|
82
|
+
if self.class.templates.keys.member? relative_path(file, @template_path)
|
83
|
+
FileUtils.mkdir_p(File.dirname(new_path)) unless File.exists?(File.dirname(new_path))
|
84
|
+
erb = ERB.new(File.read(file))
|
85
|
+
output = File.new(new_path, "w")
|
86
|
+
output << erb.result(binding)
|
87
|
+
output.close
|
88
|
+
else
|
89
|
+
FileUtils.cp file, new_path
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
self.class.create_dir.each do |d|
|
96
|
+
FileUtils.mkdir_p File.join(to_path, d)
|
97
|
+
puts "create dir #{d}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def overwrite?(path)
|
102
|
+
return true if @overwrite_all
|
103
|
+
puts "File #{path} exists. Overwrite? (y(es)/n(o)/a(ll)) "
|
104
|
+
a = STDIN.gets.downcase[0..0]
|
105
|
+
@overwrite_all = a == 'a'
|
106
|
+
@overwrite_all || a == 'y'
|
107
|
+
end
|
108
|
+
|
109
|
+
def method_missing(name, *args)
|
110
|
+
if @arguments.member? name
|
111
|
+
@arguments[name] rescue nil
|
112
|
+
else
|
113
|
+
raise NoMethodError, "no method #{name} on #{self.class.to_s}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_args
|
118
|
+
end
|
119
|
+
|
120
|
+
# returns a relative path
|
121
|
+
# relative_path("/home/user/foo/bar.rb", "/home/user") #=> "foo/bar.rb"
|
122
|
+
# relative_path("/home/user/foo/bar.rb", "/home/user/") #=> "foo/bar.rb"
|
123
|
+
# relative_path("/home/user/code/home/foo/bar.rb", "/home") #=> "user/code/home/foo/bar.rb"
|
124
|
+
def relative_path(full_path, base_path)
|
125
|
+
rel = full_path.gsub(/^#{base_path}/, '')
|
126
|
+
rel.start_with?('/') ? rel[1..-1] : rel
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rpv/generators/base'
|
2
|
+
|
3
|
+
module RPV
|
4
|
+
module Generators
|
5
|
+
class PresenterGenerator < RPV::Generators::Base
|
6
|
+
argument :name
|
7
|
+
parse 'presenter.rb.erb', 'app/presenters/#{name}.rb'
|
8
|
+
parse 'presenter_spec.rb.erb', 'spec/presenters/#{name}_spec.rb'
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@template_path = File.dirname(__FILE__) + '/templates/presenter'
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_args
|
15
|
+
@arguments[:name] = @arguments[:name].underscore + "_presenter"
|
16
|
+
@arguments[:class_name] = @arguments[:name].camelize
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.show?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.describe
|
24
|
+
"Creates a new presenter"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|