rpv_core 0.2
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.
- 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
@@ -0,0 +1,12 @@
|
|
1
|
+
So you created a new RPV app. Here is a short guide to get started:
|
2
|
+
|
3
|
+
- Add your favorite GUI framework (at the time of writing there just is rpv_swing)
|
4
|
+
- add "require 'rpv/swing'" to app/setup.rb
|
5
|
+
- Check out the generators:
|
6
|
+
- run "rpv g" (or "jruby -S rpv g") in your application's directory to see what generators there are
|
7
|
+
- Create a presenter and a view.
|
8
|
+
- Make sure your app/main.rb creates an instance of your presenter.
|
9
|
+
- Run your app with "ruby app/main.rb" (or "jruby app/main.rb" if you use rpv_swing)
|
10
|
+
- "rpv r" serves as a shortcut for that
|
11
|
+
|
12
|
+
TODO: link to GH wiki?
|
@@ -0,0 +1,11 @@
|
|
1
|
+
AppBase = File.expand_path(File.dirname(__FILE__)) unless defined?(AppBase)
|
2
|
+
#AppBase contains the to .../your_application/app
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rpv'
|
6
|
+
|
7
|
+
#autoload:
|
8
|
+
require 'rpv/autoload'
|
9
|
+
RPV::Autoload.setup_paths AppBase
|
10
|
+
|
11
|
+
#this is the right place to require any libs your application needs
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc "Run all specs"
|
5
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
6
|
+
t.ruby_opts = ["-r app/setup"]
|
7
|
+
t.spec_opts = ["--colour", "--format progress", "--loadby mtime"]
|
8
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
9
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper.rb"
|
2
|
+
|
3
|
+
describe <%= class_name %> do
|
4
|
+
describe "event handling" do
|
5
|
+
before(:all) do
|
6
|
+
@view = mock_event_publisher { |m|
|
7
|
+
#m.fires :some_event
|
8
|
+
}
|
9
|
+
@presenter = <%= class_name %>.new(:view => @view)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "setup" do
|
16
|
+
before(:each) do
|
17
|
+
@view = mock("view", :null_object => true)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# This code is a modified version of the Inflector class
|
2
|
+
# from the Ruby on Rails project (http://www.rubyonrails.com)
|
3
|
+
|
4
|
+
module RPV
|
5
|
+
module Inflector
|
6
|
+
# The reverse of +camelize+. Makes an underscored form from the expression in the string.
|
7
|
+
#
|
8
|
+
# Changes '::' to '/' to convert namespaces to paths.
|
9
|
+
#
|
10
|
+
# Examples
|
11
|
+
# "ActiveRecord".underscore #=> "active_record"
|
12
|
+
# "ActiveRecord::Errors".underscore #=> active_record/errors
|
13
|
+
def underscore()
|
14
|
+
self.to_s.gsub(/::/, '/').
|
15
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
16
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
17
|
+
tr("-", "_").
|
18
|
+
downcase
|
19
|
+
end
|
20
|
+
|
21
|
+
# Constantize tries to find a declared constant with the name specified
|
22
|
+
# in the string. It raises a NameError when the name is not in CamelCase
|
23
|
+
# or is not initialized.
|
24
|
+
#
|
25
|
+
# Examples
|
26
|
+
# "Module".constantize #=> Module
|
27
|
+
# "Class".constantize #=> Class
|
28
|
+
def constantize()
|
29
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self.to_s
|
30
|
+
raise NameError, "#{self.inspect} is not a valid constant name!"
|
31
|
+
end
|
32
|
+
|
33
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
34
|
+
end
|
35
|
+
|
36
|
+
# By default, camelize converts strings to UpperCamelCase. If the argument to camelize
|
37
|
+
# is set to ":lower" then camelize produces lowerCamelCase.
|
38
|
+
#
|
39
|
+
# camelize will also convert '/' to '::' which is useful for converting paths to namespaces
|
40
|
+
#
|
41
|
+
# Examples
|
42
|
+
# "active_record".camelize #=> "ActiveRecord"
|
43
|
+
# "active_record".camelize(:lower) #=> "activeRecord"
|
44
|
+
# "active_record/errors".camelize #=> "ActiveRecord::Errors"
|
45
|
+
# "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
|
46
|
+
def camelize(first_letter_in_uppercase = true)
|
47
|
+
if first_letter_in_uppercase
|
48
|
+
self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
49
|
+
else
|
50
|
+
self.to_s[0..0] + camelize(self.to_s)[1..-1]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class String
|
57
|
+
include RPV::Inflector
|
58
|
+
end
|
59
|
+
|
60
|
+
class Symbol
|
61
|
+
include RPV::Inflector
|
62
|
+
end
|
63
|
+
|
64
|
+
class Class
|
65
|
+
def constantize
|
66
|
+
self
|
67
|
+
end
|
68
|
+
end
|
data/lib/rpv/method.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module RPV
|
2
|
+
module OptionExtractor
|
3
|
+
def extract_options(args, *names)
|
4
|
+
args = args.find { |e| e.is_a?(Hash) } || {}
|
5
|
+
values = names.map do |name|
|
6
|
+
args[name]
|
7
|
+
end
|
8
|
+
if values.size == 1
|
9
|
+
values.first
|
10
|
+
else
|
11
|
+
values
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'rpv/event_publisher'
|
2
|
+
require 'spec/mocks'
|
3
|
+
|
4
|
+
class RPV::MockEventPublisher
|
5
|
+
include RPV::EventPublisher
|
6
|
+
|
7
|
+
# A wrapper for the private method 'fire'
|
8
|
+
def do_fire(event, *args)
|
9
|
+
fire event, *args
|
10
|
+
end
|
11
|
+
|
12
|
+
# Ignore missing methods
|
13
|
+
def method_missing(sym, *args)
|
14
|
+
Spec::Mocks::Mock.new("null object from MockEventPublisher#{sym.to_s}", :null_object => true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def mock_event_publisher
|
19
|
+
m = Class.new(RPV::MockEventPublisher)
|
20
|
+
yield m if block_given?
|
21
|
+
m.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def mock_block(*args)
|
25
|
+
yield m = mock(args)
|
26
|
+
m
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Matchers.define :fire_event do |event|
|
30
|
+
match do |actual|
|
31
|
+
actual.fires?(event.to_sym)
|
32
|
+
end
|
33
|
+
|
34
|
+
failure_message_for_should do |actual|
|
35
|
+
"expected #{actual} to fire #{event}"
|
36
|
+
end
|
37
|
+
|
38
|
+
failure_message_for_should_not do |actual|
|
39
|
+
"expected #{actual} not to fire #{event}"
|
40
|
+
end
|
41
|
+
|
42
|
+
description do
|
43
|
+
"fire #{event}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Spec::Matchers.define :fire_events do |*events|
|
48
|
+
match do |actual|
|
49
|
+
events.flatten.each do |event|
|
50
|
+
actual.should fire_event event
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
description do
|
55
|
+
"fire #{events.join(', ')}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Spec::Matchers.define :have_attr_reader do |attr|
|
60
|
+
match do |actual|
|
61
|
+
actual.respond_to?(attr)
|
62
|
+
end
|
63
|
+
|
64
|
+
failure_message_for_should do |actual|
|
65
|
+
"expected #{actual} to have an attribute reader for #{attr}"
|
66
|
+
end
|
67
|
+
|
68
|
+
failure_message_for_should_not do |actual|
|
69
|
+
"expected #{actual} not to have an attribute reader for #{attr}"
|
70
|
+
end
|
71
|
+
|
72
|
+
description do
|
73
|
+
"have an attribute reader for #{attr}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
Spec::Matchers.define :have_attr_accessor do |attr|
|
78
|
+
match do |actual|
|
79
|
+
actual.respond_to?(attr) && actual.respond_to?("#{attr}=")
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
failure_message_for_should do |actual|
|
84
|
+
"expected #{actual} to have an attribute accessor for #{attr}"
|
85
|
+
end
|
86
|
+
|
87
|
+
failure_message_for_should_not do |actual|
|
88
|
+
"expected #{actual} not to have an attribute accessor for #{attr}"
|
89
|
+
end
|
90
|
+
|
91
|
+
description do
|
92
|
+
"have an attribute accessor for #{attr}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
Spec::Matchers.define :have_attr_accessors do |*attrs|
|
97
|
+
match do |actual|
|
98
|
+
attrs.flatten.each do |attr|
|
99
|
+
actual.should have_attr_accessor attr
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
description do
|
104
|
+
"have attribute accessors for #{attrs.join(', ')}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
Spec::Matchers.define :have_attr_readers do |*attrs|
|
109
|
+
match do |actual|
|
110
|
+
attrs.flatten.each do |attr|
|
111
|
+
actual.should have_attr_reader attr
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
description do
|
116
|
+
"have attribute readers for #{attrs.join(', ')}"
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RPV
|
2
|
+
module Wiring
|
3
|
+
class Base
|
4
|
+
def initialize(args = {})
|
5
|
+
@model, @view, @presenter = args[:model], args[:view], args[:presenter]
|
6
|
+
raise ArgumentError, "View field must be given" if @view.nil? || (!@model.nil? && !@presenter.nil?)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Match the view method against a hash of constraints (like { :only => /^bar\.(.*)/, :except => /(.*)\.text$/ })
|
10
|
+
def view_match(args = {})
|
11
|
+
match(@view, args)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Match the presenter method against a hash of constraints (like { :only => /^bar\.(.*)/, :except => /(.*)\.text$/ })
|
15
|
+
def presenter_match(args = {})
|
16
|
+
match(@presenter, args)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Match the model method against a hash of constraints (like { :only => /^bar\.(.*)/, :except => /(.*)\.text$/ })
|
20
|
+
def model_match(args = {})
|
21
|
+
match(@model, args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_view(model, view, presenter)
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_model(model, view, presenter)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_presenter(model, view, presenter)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def match(method, args)
|
35
|
+
!method.nil? && (args[:only].nil? || method =~ args[:only]) && (args[:except].nil? || !(method =~ args[:except]))
|
36
|
+
end
|
37
|
+
|
38
|
+
def send_nested(obj, methods_string, *args)
|
39
|
+
methods = methods_string.to_s.split('.')
|
40
|
+
if methods.size > 1
|
41
|
+
first = methods.slice! 0
|
42
|
+
send_nested(obj.send(first), methods.join('.'), *args)
|
43
|
+
else
|
44
|
+
obj.send(methods.first, *args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rpv/wiring/converting'
|
2
|
+
|
3
|
+
module RPV
|
4
|
+
module Wiring
|
5
|
+
class Callable < Converting
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
@to_block, @from_block = args[0][:to_view], args[0][:from_view]
|
9
|
+
end
|
10
|
+
|
11
|
+
def convert_to_view(value)
|
12
|
+
@to_block.call(value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def convert_from_view(value)
|
16
|
+
@from_block.call(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rpv/wiring/base'
|
2
|
+
|
3
|
+
module RPV
|
4
|
+
module Wiring
|
5
|
+
# Abstract
|
6
|
+
class Converting < Base
|
7
|
+
def initialize(args)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def update_view(model, view, presenter)
|
12
|
+
unless @model.nil?
|
13
|
+
send_nested(view, "#{@view}=", convert_to_view(send_nested(model, @model)))
|
14
|
+
else
|
15
|
+
send_nested(view, "#{@view}=", convert_to_view(send_nested(presenter, @presenter)))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_model(model, view, presenter)
|
20
|
+
send_nested(model, "#{@model}=", convert_from_view(send_nested(view, @view))) unless @model.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_presenter(model, view, presenter)
|
24
|
+
send_nested(presenter, "#{@presenter}=", convert_from_view(send_nested(view, @view))) unless @presenter.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert_from_view(value)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert_to_view(value)
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RPV
|
2
|
+
module Wiring
|
3
|
+
module WireHelpers
|
4
|
+
module ClassMethods
|
5
|
+
def simple(*args)
|
6
|
+
Simple.new(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def unidirectional(*args)
|
10
|
+
Unidirectional.new(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def callable(*args)
|
14
|
+
Callable.new(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
base.extend(ClassMethods)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|