pakyow-presenter 0.6.1
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/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
|
+
|