pakyow-presenter 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/pakyow-presenter/CHANGES +3 -0
- data/pakyow-presenter/MIT-LICENSE +20 -0
- data/pakyow-presenter/README +0 -0
- data/pakyow-presenter/lib/pakyow-presenter.rb +13 -0
- data/pakyow-presenter/lib/presenter/base.rb +11 -0
- data/pakyow-presenter/lib/presenter/binder.rb +60 -0
- data/pakyow-presenter/lib/presenter/configuration/base.rb +12 -0
- data/pakyow-presenter/lib/presenter/configuration/presenter.rb +32 -0
- data/pakyow-presenter/lib/presenter/helpers.rb +8 -0
- data/pakyow-presenter/lib/presenter/lazy_view.rb +42 -0
- data/pakyow-presenter/lib/presenter/presenter.rb +180 -0
- data/pakyow-presenter/lib/presenter/view.rb +408 -0
- data/pakyow-presenter/lib/presenter/view_context.rb +20 -0
- data/pakyow-presenter/lib/presenter/view_lookup_store.rb +213 -0
- data/pakyow-presenter/lib/presenter/views.rb +115 -0
- metadata +111 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Bryan Powell
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
libdir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
3
|
+
|
4
|
+
# Gems
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
# Base
|
8
|
+
require 'presenter/base'
|
9
|
+
include Presenter
|
10
|
+
|
11
|
+
require 'presenter/presenter'
|
12
|
+
require 'presenter/configuration/base'
|
13
|
+
require 'presenter/helpers'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
autoload :PresenterBase, 'core/presenter_base'
|
4
|
+
autoload :ViewLookupStore, 'presenter/view_lookup_store'
|
5
|
+
autoload :View, 'presenter/view'
|
6
|
+
autoload :LazyView, 'presenter/lazy_view'
|
7
|
+
autoload :Binder, 'presenter/binder'
|
8
|
+
autoload :Views, 'presenter/views'
|
9
|
+
autoload :ViewContext, 'presenter/view_context'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class Binder
|
4
|
+
include Pakyow::GeneralHelpers
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :options
|
8
|
+
|
9
|
+
def binder_for(klass)
|
10
|
+
View.binders = {} unless View.binders
|
11
|
+
View.binders[klass.to_s.to_sym] = self
|
12
|
+
end
|
13
|
+
|
14
|
+
def options_for(*args)
|
15
|
+
self.options = {} unless self.options
|
16
|
+
self.options[args[0]] = args[1]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :bindable, :object
|
21
|
+
|
22
|
+
def initialize(bindable, object)
|
23
|
+
self.bindable = bindable
|
24
|
+
self.object = object
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_options_for(attribute)
|
28
|
+
if self.class.options
|
29
|
+
if options = self.class.options[attribute]
|
30
|
+
unless options.is_a?(Array) || options.is_a?(Hash)
|
31
|
+
options = self.send(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
return options
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def action
|
40
|
+
unless routes = Pakyow.app.restful_routes[bindable.class.name.to_sym]
|
41
|
+
Log.warn "Attempting to bind object to #{bindable.class.name.downcase}[action] but could not find restful routes for #{bindable.class.name}."
|
42
|
+
return {}
|
43
|
+
end
|
44
|
+
|
45
|
+
if id = bindable.id
|
46
|
+
self.object.add_child('<input type="hidden" name="_method" value="put">')
|
47
|
+
|
48
|
+
|
49
|
+
action = routes[:update].gsub(':id', id.to_s)
|
50
|
+
method = "post"
|
51
|
+
else
|
52
|
+
action = routes[:create]
|
53
|
+
method = "post"
|
54
|
+
end
|
55
|
+
|
56
|
+
return { :action => action, :method => method }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Configuration
|
3
|
+
class Presenter
|
4
|
+
class << self
|
5
|
+
attr_accessor :view_caching, :javascripts, :stylesheets, :view_dir, :default_view
|
6
|
+
|
7
|
+
# Location of javascripts
|
8
|
+
def javascripts
|
9
|
+
@javascripts || '/javascripts'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Location of stylesheets
|
13
|
+
def stylesheets
|
14
|
+
@stylesheets || '/stylesheets'
|
15
|
+
end
|
16
|
+
|
17
|
+
def view_dir
|
18
|
+
@view_dir || "#{Configuration::Base.app.root}/app/views"
|
19
|
+
end
|
20
|
+
|
21
|
+
def view_caching
|
22
|
+
@view_caching || false
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_view
|
26
|
+
@default_view || "pakyow.html"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class LazyView < View
|
4
|
+
|
5
|
+
def to_html(*args)
|
6
|
+
Pakyow.app.presenter.ensure_root_view_built
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_content_to_container(*args)
|
11
|
+
Pakyow.app.presenter.ensure_root_view_built
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(*args)
|
16
|
+
Pakyow.app.presenter.ensure_root_view_built
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def repeat_for(*args, &block)
|
21
|
+
Pakyow.app.presenter.ensure_root_view_built
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset_container(*args)
|
26
|
+
Pakyow.app.presenter.ensure_root_view_built
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def title=(*args)
|
31
|
+
Pakyow.app.presenter.ensure_root_view_built
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def bind(*args)
|
36
|
+
Pakyow.app.presenter.ensure_root_view_built
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class Presenter < PresenterBase
|
4
|
+
attr_accessor :current_context
|
5
|
+
|
6
|
+
#
|
7
|
+
# Methods that are called by core. This is the interface that core expects a Presenter to have
|
8
|
+
#
|
9
|
+
|
10
|
+
def reload!
|
11
|
+
load_views
|
12
|
+
end
|
13
|
+
|
14
|
+
def present_for_request(request)
|
15
|
+
@presented = false
|
16
|
+
@root_path = nil
|
17
|
+
@root_view_is_built = false
|
18
|
+
@root_view = nil
|
19
|
+
@view_path = nil
|
20
|
+
@container_name = nil
|
21
|
+
@request = request
|
22
|
+
end
|
23
|
+
|
24
|
+
def presented?
|
25
|
+
@presented
|
26
|
+
end
|
27
|
+
|
28
|
+
def content
|
29
|
+
return unless view
|
30
|
+
request_container = @request.params[:_container]
|
31
|
+
return view.to_html(request_container) if request_container
|
32
|
+
view.to_html(@container_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Methods that a controller can call to get and modify the root view.
|
37
|
+
# Some are meant to be called directly and some make up a dsl for dom modification
|
38
|
+
#
|
39
|
+
|
40
|
+
# Call these directly
|
41
|
+
#
|
42
|
+
|
43
|
+
def view_for_path(abstract_path, is_root_view=false, klass=View)
|
44
|
+
real_path = @view_lookup_store.real_path(abstract_path)
|
45
|
+
klass.new(real_path, is_root_view)
|
46
|
+
end
|
47
|
+
|
48
|
+
def view_for_class(view_class, path_override=nil)
|
49
|
+
return view_for_path(path_override, view_class.default_is_root_view, view_class) if path_override
|
50
|
+
view_for_path(view_class.default_view_path, view_class.default_is_root_view, view_class)
|
51
|
+
end
|
52
|
+
|
53
|
+
def view
|
54
|
+
ensure_root_view_built
|
55
|
+
@root_view
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_view(view)
|
59
|
+
@root_view = View.new(view)
|
60
|
+
@root_view_is_built = true
|
61
|
+
@presented = true
|
62
|
+
@view_path = nil
|
63
|
+
@root_path = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def limit_to_container(id)
|
67
|
+
@container_name = id
|
68
|
+
end
|
69
|
+
|
70
|
+
def use_view_path(path)
|
71
|
+
@view_path = path
|
72
|
+
@root_view_is_built = false
|
73
|
+
end
|
74
|
+
|
75
|
+
def view_path
|
76
|
+
@view_path
|
77
|
+
end
|
78
|
+
|
79
|
+
def use_root_view_file(abstract_view_file)
|
80
|
+
real_path = @view_lookup_store.real_path(abstract_view_file)
|
81
|
+
@root_path = real_path
|
82
|
+
@root_view_is_built = false
|
83
|
+
end
|
84
|
+
|
85
|
+
def use_root_view_at_view_path(abstract_view_dir)
|
86
|
+
@root_path = @view_lookup_store.view_info(abstract_view_dir)[:root_view]
|
87
|
+
@root_view_is_built = false
|
88
|
+
end
|
89
|
+
|
90
|
+
# This is for creating views from within a controller using the route based lookup mechanism
|
91
|
+
def view_for_view_path(v_p, name, deep = false)
|
92
|
+
v = nil
|
93
|
+
view_info = @view_lookup_store.view_info(v_p)
|
94
|
+
vpath = view_info[:views][name] if view_info
|
95
|
+
v = View.new(vpath) if vpath
|
96
|
+
if v && deep
|
97
|
+
populate_view(v, view_info[:views])
|
98
|
+
end
|
99
|
+
v
|
100
|
+
end
|
101
|
+
|
102
|
+
def populate_view_for_view_path(view, v_p)
|
103
|
+
return view unless view_info = @view_lookup_store.view_info(v_p)
|
104
|
+
views = view_info[:views]
|
105
|
+
populate_view(view, views)
|
106
|
+
view
|
107
|
+
end
|
108
|
+
|
109
|
+
# Call as part of View DSL for DOM manipulation
|
110
|
+
#
|
111
|
+
|
112
|
+
def with_container(container, &block)
|
113
|
+
ViewContext.new(self.view.find("##{container}").first).instance_eval(&block)
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Used by LazyView
|
118
|
+
#
|
119
|
+
|
120
|
+
def ensure_root_view_built
|
121
|
+
build_root_view unless @root_view_is_built
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
protected
|
126
|
+
#
|
127
|
+
|
128
|
+
def build_root_view
|
129
|
+
@root_view_is_built = true
|
130
|
+
|
131
|
+
if @view_path
|
132
|
+
v_p = @view_path
|
133
|
+
elsif @request.restful
|
134
|
+
v_p = restful_view_path(@request.restful)
|
135
|
+
elsif @request.route_spec && @request.route_spec.index(':')
|
136
|
+
v_p = StringUtils.remove_route_vars(@request.route_spec)
|
137
|
+
else
|
138
|
+
v_p = @request.env['PATH_INFO']
|
139
|
+
end
|
140
|
+
return unless v_p
|
141
|
+
return unless view_info = @view_lookup_store.view_info(v_p)
|
142
|
+
|
143
|
+
@presented = true
|
144
|
+
@root_path ||= view_info[:root_view]
|
145
|
+
@root_view = LazyView.new(@root_path, true)
|
146
|
+
views = view_info[:views]
|
147
|
+
populate_view(self.view, views)
|
148
|
+
end
|
149
|
+
|
150
|
+
def restful_view_path(restful_info)
|
151
|
+
if restful_info[:restful_action] == :show
|
152
|
+
"#{StringUtils.remove_route_vars(@request.route_spec)}/show"
|
153
|
+
else
|
154
|
+
StringUtils.remove_route_vars(@request.route_spec)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def load_views
|
159
|
+
@view_lookup_store = ViewLookupStore.new("#{Configuration::Presenter.view_dir}")
|
160
|
+
end
|
161
|
+
|
162
|
+
# populates the top_view using view_store data by recursively building
|
163
|
+
# and substituting in child views named in the structure
|
164
|
+
def populate_view(top_view, views)
|
165
|
+
containers = top_view.elements_with_ids
|
166
|
+
containers.each {|e|
|
167
|
+
name = e.attr("id")
|
168
|
+
path = views[name]
|
169
|
+
if path
|
170
|
+
v = populate_view(View.new(path), views)
|
171
|
+
top_view.reset_container(name)
|
172
|
+
top_view.add_content_to_container(v, name)
|
173
|
+
end
|
174
|
+
}
|
175
|
+
top_view
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,408 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class View
|
4
|
+
class << self
|
5
|
+
attr_accessor :binders, :cache, :default_view_path, :default_is_root_view
|
6
|
+
|
7
|
+
def view_path(dvp, dirv=false)
|
8
|
+
self.default_view_path = dvp
|
9
|
+
self.default_is_root_view = dirv
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :doc
|
14
|
+
|
15
|
+
def initialize(arg=nil, is_root_view=false)
|
16
|
+
arg = self.class.default_view_path if arg.nil? && self.class.default_view_path
|
17
|
+
is_root_view = self.class.default_is_root_view if arg.nil? && self.class.default_is_root_view
|
18
|
+
|
19
|
+
if arg.is_a?(Nokogiri::XML::Element)
|
20
|
+
@doc = arg
|
21
|
+
elsif arg.is_a?(Pakyow::Presenter::Views)
|
22
|
+
@doc = arg.first.doc.dup
|
23
|
+
elsif arg.is_a?(Pakyow::Presenter::View)
|
24
|
+
@doc = arg.doc.dup
|
25
|
+
elsif arg.is_a?(String)
|
26
|
+
if arg[0, 1] == '/'
|
27
|
+
view_path = "#{Configuration::Presenter.view_dir}#{arg}"
|
28
|
+
else
|
29
|
+
view_path = "#{Configuration::Presenter.view_dir}/#{arg}"
|
30
|
+
end
|
31
|
+
# Only load one time if view caching is enabled
|
32
|
+
self.class.cache ||= {}
|
33
|
+
|
34
|
+
if !self.class.cache.has_key?(view_path) || !Configuration::Base.presenter.view_caching
|
35
|
+
if is_root_view then
|
36
|
+
self.class.cache[view_path] = Nokogiri::HTML::Document.parse(File.read(view_path))
|
37
|
+
else
|
38
|
+
self.class.cache[view_path] = Nokogiri::HTML.fragment(File.read(view_path))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@doc = self.class.cache[view_path].dup
|
43
|
+
else
|
44
|
+
raise ArgumentError, "No View for you! Come back, one year."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_content_to_container(content, container)
|
49
|
+
# TODO This .css call works but the equivalent .xpath call doesn't
|
50
|
+
# Need to investigate why since the .css call is internally turned into a .xpath call
|
51
|
+
if @doc && o = @doc.css("##{container}").first
|
52
|
+
content = content.doc unless content.class == String || content.class == Nokogiri::HTML::DocumentFragment || content.class == Nokogiri::XML::Element
|
53
|
+
o.add_child(content)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_resource(*args)
|
58
|
+
type, resource, options = args
|
59
|
+
options ||= {}
|
60
|
+
|
61
|
+
content = case type
|
62
|
+
when :js then '<script src="' + Pakyow::Configuration::Presenter.javascripts + '/' + resource.to_s + '.js"></script>'
|
63
|
+
when :css then '<link href="' + Pakyow::Configuration::Presenter.stylesheets + '/' + resource.to_s + '.css" rel="stylesheet" media="' + (options[:media] || 'screen, projection') + '" type="text/css">'
|
64
|
+
end
|
65
|
+
|
66
|
+
if self.doc.fragment? || self.doc.element?
|
67
|
+
self.doc.add_previous_sibling(content)
|
68
|
+
else
|
69
|
+
self.doc.xpath("//head/*[1]").before(content)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove_resource(*args)
|
74
|
+
type, resource, options = args
|
75
|
+
options ||= {}
|
76
|
+
|
77
|
+
case type
|
78
|
+
when :js then self.doc.css("script[src='#{Pakyow::Configuration::Presenter.javascripts}/#{resource}.js']").remove
|
79
|
+
when :css then self.doc.css("link[href='#{Pakyow::Configuration::Presenter.stylesheets}/#{resource}.css']").remove
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def find(element)
|
84
|
+
group = Views.new
|
85
|
+
@doc.css(element).each {|e| group << View.new(e)}
|
86
|
+
|
87
|
+
return group
|
88
|
+
end
|
89
|
+
|
90
|
+
def in_context(&block)
|
91
|
+
ViewContext.new(self).instance_eval(&block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def bind(object, type = nil)
|
95
|
+
type = type || StringUtils.underscore(object.class.name)
|
96
|
+
|
97
|
+
# This works: .//*
|
98
|
+
# Not this: .//*[@itemprop or @name]
|
99
|
+
# WTF!
|
100
|
+
#
|
101
|
+
@doc.xpath('.//*').each do |o|
|
102
|
+
if attribute = o.get_attribute('itemprop')
|
103
|
+
selector = attribute
|
104
|
+
elsif name = o.get_attribute('name')
|
105
|
+
selector = name
|
106
|
+
else
|
107
|
+
next
|
108
|
+
end
|
109
|
+
|
110
|
+
next unless attribute
|
111
|
+
|
112
|
+
if selector.include?('[')
|
113
|
+
type_len = type.length
|
114
|
+
object_type = selector[0,type_len]
|
115
|
+
attribute = selector[type_len + 1, attribute.length - type_len - 2]
|
116
|
+
else
|
117
|
+
object_type = nil
|
118
|
+
attribute = selector
|
119
|
+
end
|
120
|
+
|
121
|
+
next if !object_type.nil? && object_type != type
|
122
|
+
|
123
|
+
binding = {
|
124
|
+
:element => o,
|
125
|
+
:attribute => attribute.to_sym,
|
126
|
+
:selector => selector
|
127
|
+
}
|
128
|
+
|
129
|
+
bind_object_to_binding(object, binding, object_type.nil?)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def repeat_for(objects, &block)
|
134
|
+
if o = @doc
|
135
|
+
objects.each do |object|
|
136
|
+
view = View.new(self)
|
137
|
+
view.bind(object)
|
138
|
+
ViewContext.new(view).instance_exec(object, &block) if block_given?
|
139
|
+
|
140
|
+
o.add_previous_sibling(view.doc)
|
141
|
+
end
|
142
|
+
|
143
|
+
o.remove
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def reset_container(container)
|
148
|
+
if @doc && o = @doc.css("*[id='#{container}']").first
|
149
|
+
o.inner_html = ''
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def title=(title)
|
154
|
+
if @doc
|
155
|
+
if o = @doc.css('title').first
|
156
|
+
o.inner_html = title
|
157
|
+
else
|
158
|
+
if o = @doc.css('head').first
|
159
|
+
o.add_child(Nokogiri::HTML::fragment("<title>#{title}</title>"))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def title
|
166
|
+
o = @doc.css('title').first
|
167
|
+
o.inner_html if o
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_html(container = nil)
|
171
|
+
if container
|
172
|
+
if o = @doc.css('#' + container.to_s).first
|
173
|
+
o.inner_html
|
174
|
+
else
|
175
|
+
''
|
176
|
+
end
|
177
|
+
else
|
178
|
+
@doc.to_html
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
alias :to_s :to_html
|
183
|
+
|
184
|
+
# Allows multiple attributes to be set at once.
|
185
|
+
# root_view.find(selector).attributes(:class => my_class, :style => my_style)
|
186
|
+
#
|
187
|
+
def attributes(*args)
|
188
|
+
if args.empty?
|
189
|
+
@previous_method = :attributes
|
190
|
+
return self
|
191
|
+
else
|
192
|
+
args[0].each_pair { |name, value|
|
193
|
+
@previous_method = :attributes
|
194
|
+
self.send(name.to_sym, value)
|
195
|
+
}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def remove
|
200
|
+
self.doc.remove
|
201
|
+
end
|
202
|
+
|
203
|
+
alias :delete :remove
|
204
|
+
|
205
|
+
def add_class(val)
|
206
|
+
self.doc['class'] = "#{self.doc['class']} #{val}".strip
|
207
|
+
end
|
208
|
+
|
209
|
+
def remove_class(val)
|
210
|
+
self.doc['class'] = self.doc['class'].gsub(val.to_s, '').strip if self.doc['class']
|
211
|
+
end
|
212
|
+
|
213
|
+
def has_class(val)
|
214
|
+
self.doc['class'].include? val
|
215
|
+
end
|
216
|
+
|
217
|
+
def clear
|
218
|
+
self.doc.inner_html = ''
|
219
|
+
end
|
220
|
+
|
221
|
+
def text
|
222
|
+
self.doc.inner_text
|
223
|
+
end
|
224
|
+
|
225
|
+
def content
|
226
|
+
self.doc.inner_html
|
227
|
+
end
|
228
|
+
|
229
|
+
alias :html :content
|
230
|
+
|
231
|
+
def content=(content)
|
232
|
+
return unless content
|
233
|
+
self.doc.inner_html = content.to_s
|
234
|
+
end
|
235
|
+
|
236
|
+
alias :html= :content=
|
237
|
+
|
238
|
+
def append(content)
|
239
|
+
self.doc.inner_html += content.to_s
|
240
|
+
end
|
241
|
+
|
242
|
+
alias :render :append
|
243
|
+
|
244
|
+
def +(value)
|
245
|
+
if @previous_method
|
246
|
+
append_value(val)
|
247
|
+
else
|
248
|
+
super
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def <<(value)
|
253
|
+
if @previous_method
|
254
|
+
append_value(val)
|
255
|
+
else
|
256
|
+
super
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def method_missing(method, *args)
|
261
|
+
return unless @previous_method == :attributes
|
262
|
+
@previous_method = nil
|
263
|
+
|
264
|
+
if method.to_s.include?('=')
|
265
|
+
attribute = method.to_s.gsub('=', '')
|
266
|
+
value = args[0]
|
267
|
+
|
268
|
+
self.doc[attribute] = value
|
269
|
+
else
|
270
|
+
return self.doc[method.to_s]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def class(*args)
|
275
|
+
if @previous_method == :attributes
|
276
|
+
method_missing(:class, *args)
|
277
|
+
else
|
278
|
+
super
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def id
|
283
|
+
if @previous_method == :attributes
|
284
|
+
method_missing(:id)
|
285
|
+
else
|
286
|
+
super
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def elements_with_ids
|
291
|
+
elements = []
|
292
|
+
@doc.traverse {|e|
|
293
|
+
if e.has_attribute?("id")
|
294
|
+
elements << e
|
295
|
+
end
|
296
|
+
}
|
297
|
+
elements
|
298
|
+
end
|
299
|
+
|
300
|
+
protected
|
301
|
+
|
302
|
+
def append_value(value_to_append)
|
303
|
+
case @previous_method
|
304
|
+
when :content
|
305
|
+
append(value_to_append)
|
306
|
+
end
|
307
|
+
|
308
|
+
@previous_method = nil
|
309
|
+
end
|
310
|
+
|
311
|
+
def bind_object_to_binding(object, binding, wild = false)
|
312
|
+
binder = nil
|
313
|
+
|
314
|
+
# fetch value
|
315
|
+
if object.is_a? Hash
|
316
|
+
value = object[binding[:attribute]]
|
317
|
+
else
|
318
|
+
if View.binders
|
319
|
+
b = View.binders[object.class.to_s.to_sym] and binder = b.new(object, binding[:element])
|
320
|
+
end
|
321
|
+
|
322
|
+
if binder && binder.class.method_defined?(binding[:attribute])
|
323
|
+
value = binder.send(binding[:attribute])
|
324
|
+
else
|
325
|
+
if wild && !object.class.method_defined?(binding[:attribute])
|
326
|
+
return
|
327
|
+
elsif Configuration::Base.app.dev_mode == true && !object.class.method_defined?(binding[:attribute])
|
328
|
+
Log.warn("Attempting to bind object to #{binding[:html_tag]}#{binding[:selector].gsub('*', '').gsub('\'', '')} but #{object.class.name}##{binding[:attribute]} is not defined.")
|
329
|
+
return
|
330
|
+
else
|
331
|
+
value = object.send(binding[:attribute])
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
if value.is_a? Hash
|
337
|
+
value.each do |k, v|
|
338
|
+
if k == :content
|
339
|
+
bind_value_to_binding(v, binding, binder)
|
340
|
+
else
|
341
|
+
binding[:element][k.to_s] = v.to_s
|
342
|
+
end
|
343
|
+
end
|
344
|
+
else
|
345
|
+
bind_value_to_binding(value, binding, binder)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def bind_value_to_binding(value, binding, binder)
|
350
|
+
if !self.self_closing_tag?(binding[:element].name)
|
351
|
+
if binding[:element].name == 'select'
|
352
|
+
if binder
|
353
|
+
if options = binder.fetch_options_for(binding[:attribute])
|
354
|
+
html = ''
|
355
|
+
is_group = false
|
356
|
+
|
357
|
+
options.each do |opt|
|
358
|
+
if opt.is_a?(Array)
|
359
|
+
if opt.first.is_a?(Array)
|
360
|
+
opt.each do |opt2|
|
361
|
+
html << '<option value="' + opt2[0].to_s + '">' + opt2[1].to_s + '</option>'
|
362
|
+
end
|
363
|
+
else
|
364
|
+
html << '<option value="' + opt[0].to_s + '">' + opt[1].to_s + '</option>'
|
365
|
+
end
|
366
|
+
else
|
367
|
+
html << "</optgroup>" if is_group
|
368
|
+
html << '<optgroup label="' + opt.to_s + '">'
|
369
|
+
is_group = true
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
html << "</optgroup>" if is_group
|
374
|
+
|
375
|
+
binding[:element].inner_html = html
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
if opt = binding[:element].css('option[value="' + value.to_s + '"]').first
|
380
|
+
opt['selected'] = 'selected'
|
381
|
+
end
|
382
|
+
else
|
383
|
+
binding[:element].inner_html = value.to_s
|
384
|
+
end
|
385
|
+
elsif binding[:element].name == 'input' && binding[:element][:type] == 'checkbox'
|
386
|
+
if value == true || binding[:element].attributes['value'].value == value.to_s
|
387
|
+
binding[:element]['checked'] = 'checked'
|
388
|
+
else
|
389
|
+
binding[:element].delete('checked')
|
390
|
+
end
|
391
|
+
elsif binding[:element].name == 'input' && binding[:element][:type] == 'radio'
|
392
|
+
if binding[:element].attributes['value'].value == value.to_s
|
393
|
+
binding[:element]['checked'] = 'checked'
|
394
|
+
else
|
395
|
+
binding[:element].delete('checked')
|
396
|
+
end
|
397
|
+
else
|
398
|
+
binding[:element]['value'] = value.to_s
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def self_closing_tag?(tag)
|
403
|
+
%w[area base basefont br hr input img link meta].include? tag
|
404
|
+
end
|
405
|
+
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class ViewContext
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def context
|
12
|
+
@context
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(method, *args)
|
16
|
+
Pakyow.app.presenter.current_context.send(method, *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class ViewLookupStore
|
4
|
+
|
5
|
+
# @view_store is a hash structured as:
|
6
|
+
# {
|
7
|
+
# :view_dirs => {
|
8
|
+
# "/route" => {
|
9
|
+
# :root_view => "path/to/root/view",
|
10
|
+
# :views => {
|
11
|
+
# "view1_name" => "path/to/view1",
|
12
|
+
# "view2_name" => "path/to/view2"
|
13
|
+
# }
|
14
|
+
# },
|
15
|
+
# "/route/sub" => {
|
16
|
+
# :root_view => "path/to/root/view",
|
17
|
+
# :views => {
|
18
|
+
# "view1_name" => "path/to/view1",
|
19
|
+
# "view2_name" => "path/to/view2"
|
20
|
+
# }
|
21
|
+
# }
|
22
|
+
# },
|
23
|
+
# :abstract_paths => {
|
24
|
+
# "/abstract/path/file.html" => "/abstract.root1/path/file.html",
|
25
|
+
# "/some/other/path" => "/some/other.root1/path.root2"
|
26
|
+
# }
|
27
|
+
# }
|
28
|
+
# This takes into account that a view directory may have a .root suffix.
|
29
|
+
# This not only determines the root view for that route (and sub-routes) but that
|
30
|
+
# the route doesn't include the suffix but the path to a view does
|
31
|
+
|
32
|
+
attr_reader(:view_dir)
|
33
|
+
|
34
|
+
def initialize(view_dir)
|
35
|
+
@view_store = {:view_dirs => {}, :abstract_paths => {}}
|
36
|
+
return unless File.exist?(view_dir)
|
37
|
+
|
38
|
+
@view_dir = view_dir
|
39
|
+
|
40
|
+
# wack the ./ at the beginning if it's there
|
41
|
+
view_dir = view_dir.sub(/^\.\//,'')
|
42
|
+
|
43
|
+
# making this a variable in case we change whether we store relative or absolute paths to views
|
44
|
+
absolute_path_prefix = view_dir # set to '' to store absolute paths
|
45
|
+
|
46
|
+
default_views = {} # view_basename => path_to_view.html
|
47
|
+
if File.exist?(view_dir) then
|
48
|
+
default_root_view_file_path = "#{absolute_path_prefix}/#{Configuration::Presenter.default_view}"
|
49
|
+
# See if the top level index directory overrides the default root view
|
50
|
+
index_dirs = Dir.entries(view_dir).partition{|e| File.directory?("#{absolute_path_prefix}/#{e}") && e.start_with?('index.')}[0]
|
51
|
+
if index_dirs.length == 1
|
52
|
+
default_root_view_file_path = "#{absolute_path_prefix}/#{StringUtils.split_at_last_dot(index_dirs[0])[1]}.html"
|
53
|
+
end
|
54
|
+
# The logic depends on this traversing top down
|
55
|
+
DirUtils.walk_dir(view_dir) { |vpath|
|
56
|
+
if File.directory?(vpath)
|
57
|
+
parent,route = pakyow_path_to_route_and_parent(vpath, view_dir, :dir)
|
58
|
+
# root_view is same as parent unless this route overrides it
|
59
|
+
# views are a copy of parent views
|
60
|
+
route_root_path = @view_store[:view_dirs][parent] ? @view_store[:view_dirs][parent][:root_view] : default_root_view_file_path
|
61
|
+
route_views = @view_store[:view_dirs][parent] ? deep_hash_clone(@view_store[:view_dirs][parent][:views]) : deep_hash_clone(default_views)
|
62
|
+
# see if this route overrides root_view
|
63
|
+
route_part, root_part = StringUtils.split_at_last_dot(vpath)
|
64
|
+
if root_part && root_part.include?('/')
|
65
|
+
route_part, root_part = vpath, nil
|
66
|
+
end
|
67
|
+
if root_part
|
68
|
+
if File.exist?("#{vpath}/#{root_part}.html")
|
69
|
+
route_root_path = "#{vpath}/#{root_part}.html".sub(absolute_path_prefix, '')
|
70
|
+
elsif route_views[root_part]
|
71
|
+
route_root_path = route_views[root_part]
|
72
|
+
else
|
73
|
+
if Configuration::Base.app.dev_mode == true
|
74
|
+
Log.warn("Root view #{root_part} referenced in #{vpath.sub(absolute_path_prefix, '')} was not found.")
|
75
|
+
else
|
76
|
+
Log.error("Root view #{root_part} referenced in #{vpath.sub(absolute_path_prefix, '')} was not found.")
|
77
|
+
raise "Root view #{root_part} referenced in #{vpath.sub(absolute_path_prefix, '')} was not found."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
@view_store[:view_dirs][route] =
|
82
|
+
{
|
83
|
+
:root_view => route_root_path,
|
84
|
+
:views => route_views
|
85
|
+
}
|
86
|
+
# set the abstract path for this dir
|
87
|
+
if route == '/'
|
88
|
+
r_p = '/'
|
89
|
+
else
|
90
|
+
r_p = vpath.sub(absolute_path_prefix, '')
|
91
|
+
end
|
92
|
+
@view_store[:abstract_paths][route] = r_p
|
93
|
+
# duplicate real path under routes permuted with leading/trailing slash
|
94
|
+
permute_route(route).each { |r| @view_store[:abstract_paths][r] = r_p } unless route == '/'
|
95
|
+
else
|
96
|
+
# files here are direct overrides of the route's views
|
97
|
+
parent,route = pakyow_path_to_route_and_parent(vpath, view_dir, :file)
|
98
|
+
view_key = File.basename(vpath,".*")
|
99
|
+
unless @view_store[:view_dirs][route]
|
100
|
+
@view_store[:view_dirs][route] = deep_hash_clone(@view_store[:view_dirs][parent])
|
101
|
+
end
|
102
|
+
@view_store[:view_dirs][route][:views][view_key] = vpath.sub(absolute_path_prefix, '')
|
103
|
+
# see if view overrides the root view
|
104
|
+
if File.basename(@view_store[:view_dirs][route][:root_view],".*") == view_key
|
105
|
+
@view_store[:view_dirs][route][:root_view] = vpath.sub(absolute_path_prefix, '')
|
106
|
+
end
|
107
|
+
# set the abstract path for this file
|
108
|
+
# duplicating real path under route without the leading slash
|
109
|
+
r_p = vpath.sub(absolute_path_prefix, '')
|
110
|
+
if route == '/'
|
111
|
+
@view_store[:abstract_paths]["/#{File.basename(vpath)}"] = r_p
|
112
|
+
@view_store[:abstract_paths][File.basename(vpath)] = r_p
|
113
|
+
else
|
114
|
+
route_with_leading_slash = "#{route}/#{File.basename(vpath)}"
|
115
|
+
route_without_leading_slash = route_with_leading_slash.sub('/','')
|
116
|
+
@view_store[:abstract_paths][route_with_leading_slash] = r_p
|
117
|
+
@view_store[:abstract_paths][route_without_leading_slash] = r_p
|
118
|
+
end
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
# adjust @view_store '.../index' entries to override the parent
|
124
|
+
@view_store[:view_dirs].each_pair {|route,info|
|
125
|
+
next unless File.basename(route) == "index"
|
126
|
+
parent = File.dirname(route)
|
127
|
+
@view_store[:view_dirs][parent] = info
|
128
|
+
}
|
129
|
+
# adjust @view_store entries to have a '.../index' counterpart where missing
|
130
|
+
index_counterparts = {}
|
131
|
+
@view_store[:view_dirs].each_pair {|route,info|
|
132
|
+
next if File.basename(route) == "index" || @view_store[:view_dirs]["#{route}/index"]
|
133
|
+
if route == "/"
|
134
|
+
index_counterparts["/index"] = info
|
135
|
+
else
|
136
|
+
index_counterparts["#{route}/index"] = info
|
137
|
+
end
|
138
|
+
}
|
139
|
+
index_counterparts.each_pair { |route,info|
|
140
|
+
@view_store[:view_dirs][route] = info
|
141
|
+
}
|
142
|
+
# Duplicate the info for each combination of route with and without a leading and ending slash
|
143
|
+
# All current keys have a leading slash and no trailing slash
|
144
|
+
slash_permutations = {}
|
145
|
+
@view_store[:view_dirs].each_pair {|route0,info|
|
146
|
+
unless route0 == '/' then
|
147
|
+
route1, route2, route3 = permute_route(route0)
|
148
|
+
slash_permutations[route1] = info
|
149
|
+
slash_permutations[route2] = info
|
150
|
+
slash_permutations[route3] = info
|
151
|
+
end
|
152
|
+
}
|
153
|
+
slash_permutations.each_pair { |route,info|
|
154
|
+
@view_store[:view_dirs][route] = info
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def view_info(route = nil)
|
159
|
+
if route
|
160
|
+
return @view_store[:view_dirs][route]
|
161
|
+
else
|
162
|
+
return @view_store[:view_dirs]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def real_path(abstract_path)
|
167
|
+
@view_store[:abstract_paths][abstract_path]
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
# path can be of the form prefix_path/this/route.root1/overrides/some.root2/root
|
173
|
+
# returns the path without the .root_view parts
|
174
|
+
def pakyow_path_to_route_and_parent(path, path_prefix, file_or_dir)
|
175
|
+
return "","/" if path == path_prefix
|
176
|
+
route_path = path.sub("#{path_prefix}/", "")
|
177
|
+
unless route_path.include?("/")
|
178
|
+
return "","/" if :file == file_or_dir
|
179
|
+
return "/","/#{StringUtils.split_at_last_dot(route_path)[0]}"
|
180
|
+
end
|
181
|
+
route = ""
|
182
|
+
parent = ""
|
183
|
+
segments = route_path.split('/')
|
184
|
+
segments.each_with_index {|s,i|
|
185
|
+
next if (i >= segments.length-1 && :file == file_or_dir)
|
186
|
+
route_part = StringUtils.split_at_last_dot(s)[0]
|
187
|
+
route << "/#{route_part}"
|
188
|
+
next if (i >= segments.length-2 && :file == file_or_dir) || (i >= segments.length-1 && :dir == file_or_dir)
|
189
|
+
parent << "/#{route_part}"
|
190
|
+
}
|
191
|
+
parent = "/" if parent == ""
|
192
|
+
return parent,route
|
193
|
+
end
|
194
|
+
|
195
|
+
# Gonna just use Marshal for now.
|
196
|
+
# Can change later if needed since we only need to work
|
197
|
+
# on hashes of symbols, strings and hashes.
|
198
|
+
def deep_hash_clone(h)
|
199
|
+
Marshal.load(Marshal.dump(h))
|
200
|
+
end
|
201
|
+
|
202
|
+
# Takes a route with a leading slash and no trailing slash (/route) and
|
203
|
+
# returns the three other permutaions (/route/, route/, and route).
|
204
|
+
def permute_route(route0)
|
205
|
+
route3 = route0.sub('/','')
|
206
|
+
route2 = "#{route3}/"
|
207
|
+
route1 = "#{route0}/"
|
208
|
+
return route1,route2,route3
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class Views
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@views = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def each
|
11
|
+
@views.each { |v| yield(v) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def in_context(&block)
|
15
|
+
ViewContext.new(self).instance_eval(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def attributes(*args)
|
19
|
+
self.each {|e| e.attributes(*args)}
|
20
|
+
return self
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove
|
24
|
+
self.each {|e| e.remove}
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :delete :remove
|
28
|
+
|
29
|
+
def add_class(val)
|
30
|
+
self.each {|e| e.add_class(val)}
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove_class(val)
|
34
|
+
self.each {|e| e.remove_class(val)}
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear
|
38
|
+
self.each {|e| e.clear}
|
39
|
+
end
|
40
|
+
|
41
|
+
def text
|
42
|
+
self.map { |v| v.text }
|
43
|
+
end
|
44
|
+
|
45
|
+
def content
|
46
|
+
self.map { |v| v.content }
|
47
|
+
end
|
48
|
+
|
49
|
+
alias :html :content
|
50
|
+
|
51
|
+
def content=(content)
|
52
|
+
self.each {|e| e.content = content}
|
53
|
+
end
|
54
|
+
|
55
|
+
alias :html= :content=
|
56
|
+
|
57
|
+
def to_html
|
58
|
+
self.map { |v| v.to_html }.join('')
|
59
|
+
end
|
60
|
+
|
61
|
+
alias :to_s :to_html
|
62
|
+
|
63
|
+
def append(content)
|
64
|
+
self.each {|e| e.append(content)}
|
65
|
+
end
|
66
|
+
|
67
|
+
alias :render :append
|
68
|
+
|
69
|
+
def +(val)
|
70
|
+
self.each {|e| e + val}
|
71
|
+
end
|
72
|
+
|
73
|
+
def <<(val)
|
74
|
+
if val.is_a? View
|
75
|
+
@views << val
|
76
|
+
else
|
77
|
+
self.each {|e| e << val}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def method_missing(method, *args)
|
82
|
+
if method.to_s.include?('=')
|
83
|
+
self.each {|e| e.send(method, *args)}
|
84
|
+
else
|
85
|
+
self.map {|e| e.send(method, *args)}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def class(*args)
|
90
|
+
method_missing(:class, *args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def id
|
94
|
+
method_missing(:id)
|
95
|
+
end
|
96
|
+
|
97
|
+
def repeat_for(objects, &block)
|
98
|
+
# Repeat for first match
|
99
|
+
self.first.repeat_for(objects, &block)
|
100
|
+
|
101
|
+
# Remove other matches
|
102
|
+
self.each {|e| e.doc.remove}
|
103
|
+
end
|
104
|
+
|
105
|
+
def bind(object)
|
106
|
+
self.each {|e| e.bind(object)}
|
107
|
+
end
|
108
|
+
|
109
|
+
def find(element)
|
110
|
+
views = Views.new
|
111
|
+
self.each {|e| e.find(element, &block).each { |v| views << v }}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pakyow-presenter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 5
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 6
|
9
|
+
- 1
|
10
|
+
version: 0.6.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Bryan Powell
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-20 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: pakyow-core
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - "="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 5
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 6
|
32
|
+
- 1
|
33
|
+
version: 0.6.1
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: nokogiri
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 7
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 4
|
48
|
+
version: "1.4"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
description: pakyow-presenter
|
52
|
+
email: bryan@metabahn.com
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- pakyow-presenter/CHANGES
|
61
|
+
- pakyow-presenter/README
|
62
|
+
- pakyow-presenter/MIT-LICENSE
|
63
|
+
- pakyow-presenter/lib/pakyow-presenter.rb
|
64
|
+
- pakyow-presenter/lib/presenter/base.rb
|
65
|
+
- pakyow-presenter/lib/presenter/binder.rb
|
66
|
+
- pakyow-presenter/lib/presenter/configuration/base.rb
|
67
|
+
- pakyow-presenter/lib/presenter/configuration/presenter.rb
|
68
|
+
- pakyow-presenter/lib/presenter/helpers.rb
|
69
|
+
- pakyow-presenter/lib/presenter/lazy_view.rb
|
70
|
+
- pakyow-presenter/lib/presenter/presenter.rb
|
71
|
+
- pakyow-presenter/lib/presenter/view.rb
|
72
|
+
- pakyow-presenter/lib/presenter/view_context.rb
|
73
|
+
- pakyow-presenter/lib/presenter/view_lookup_store.rb
|
74
|
+
- pakyow-presenter/lib/presenter/views.rb
|
75
|
+
homepage: http://pakyow.com
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
|
81
|
+
require_paths:
|
82
|
+
- pakyow-presenter/lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 57
|
89
|
+
segments:
|
90
|
+
- 1
|
91
|
+
- 8
|
92
|
+
- 7
|
93
|
+
version: 1.8.7
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project: pakyow-presenter
|
106
|
+
rubygems_version: 1.8.8
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: pakyow-presenter
|
110
|
+
test_files: []
|
111
|
+
|