rpv_core 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/Rakefile +3 -0
  2. data/bin/rpv +68 -0
  3. data/lib/rpv/autoload.rb +36 -0
  4. data/lib/rpv/base_presenter.rb +41 -0
  5. data/lib/rpv/event_publisher.rb +125 -0
  6. data/lib/rpv/generators/app_generator.rb +17 -0
  7. data/lib/rpv/generators/base.rb +130 -0
  8. data/lib/rpv/generators/presenter_generator.rb +28 -0
  9. data/lib/rpv/generators/templates/app/README +12 -0
  10. data/lib/rpv/generators/templates/app/Rakefile +3 -0
  11. data/lib/rpv/generators/templates/app/app/main.rb +4 -0
  12. data/lib/rpv/generators/templates/app/app/setup.rb +11 -0
  13. data/lib/rpv/generators/templates/app/spec/spec_helper.rb +5 -0
  14. data/lib/rpv/generators/templates/app/tasks/rspec.rake +9 -0
  15. data/lib/rpv/generators/templates/presenter/presenter.rb.erb +10 -0
  16. data/lib/rpv/generators/templates/presenter/presenter_spec.rb.erb +22 -0
  17. data/lib/rpv/inflector.rb +68 -0
  18. data/lib/rpv/method.rb +13 -0
  19. data/lib/rpv/method_helper.rb +9 -0
  20. data/lib/rpv/option_extractor.rb +15 -0
  21. data/lib/rpv/spec_helper.rb +118 -0
  22. data/lib/rpv/wiring/base.rb +49 -0
  23. data/lib/rpv/wiring/callable.rb +20 -0
  24. data/lib/rpv/wiring/converting.rb +36 -0
  25. data/lib/rpv/wiring/helpers.rb +23 -0
  26. data/lib/rpv/wiring/simple.rb +19 -0
  27. data/lib/rpv/wiring/unidirectional.rb +38 -0
  28. data/lib/rpv/wiring.rb +4 -0
  29. data/lib/rpv.rb +20 -0
  30. data/spec/autoload_spec.rb +24 -0
  31. data/spec/base_presenter_spec.rb +16 -0
  32. data/spec/event_publisher_spec.rb +121 -0
  33. data/spec/files/autoload/b/some_module.rb +2 -0
  34. data/spec/files/autoload/some_class.rb +3 -0
  35. data/spec/files/bar/presenter.rb.erb +5 -0
  36. data/spec/files/bar/view.rb.erb +3 -0
  37. data/spec/files/foo/Rakefile +0 -0
  38. data/spec/files/foo/app/main.rb.erb +1 -0
  39. data/spec/files/foo/app/models/templates/lala.html.erb +2 -0
  40. data/spec/generators/base_spec.rb +81 -0
  41. data/spec/generators/presenter_spec.rb +27 -0
  42. data/spec/method_helper_spec.rb +19 -0
  43. data/spec/method_spec.rb +12 -0
  44. data/spec/option_extractor_spec.rb +22 -0
  45. data/spec/spec_helper.rb +1 -0
  46. data/spec/spec_helper_spec.rb +15 -0
  47. data/spec/wiring/base_spec.rb +17 -0
  48. data/spec/wiring/callable_spec.rb +50 -0
  49. data/spec/wiring/helpers_spec.rb +29 -0
  50. data/spec/wiring/simple_spec.rb +47 -0
  51. data/spec/wiring/unidirectional_spec.rb +46 -0
  52. data/tasks/rdoc.rake +10 -0
  53. data/tasks/rspec.rake +10 -0
  54. metadata +118 -0
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ Dir.glob("tasks/**/*.rake").each do |rake_file|
2
+ load File.expand_path(File.dirname(__FILE__) + "/" + rake_file)
3
+ end
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
+
@@ -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