kiosk 0.0.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/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +25 -0
- data/Rakefile +26 -0
- data/lib/kiosk.rb +58 -0
- data/lib/kiosk/bad_config.rb +5 -0
- data/lib/kiosk/cacheable.rb +6 -0
- data/lib/kiosk/cacheable/connection.rb +102 -0
- data/lib/kiosk/cacheable/resource.rb +90 -0
- data/lib/kiosk/cdn.rb +28 -0
- data/lib/kiosk/claim.rb +12 -0
- data/lib/kiosk/claim/node_claim.rb +44 -0
- data/lib/kiosk/claim/path_claim.rb +11 -0
- data/lib/kiosk/claimed_node.rb +9 -0
- data/lib/kiosk/controller.rb +61 -0
- data/lib/kiosk/document.rb +6 -0
- data/lib/kiosk/indexer.rb +5 -0
- data/lib/kiosk/indexer/adapter.rb +14 -0
- data/lib/kiosk/indexer/adapter/base.rb +10 -0
- data/lib/kiosk/indexer/adapter/thinking_sphinx_adapter.rb +266 -0
- data/lib/kiosk/localizable.rb +5 -0
- data/lib/kiosk/localizable/resource.rb +30 -0
- data/lib/kiosk/localizer.rb +17 -0
- data/lib/kiosk/origin.rb +20 -0
- data/lib/kiosk/prospective_node.rb +18 -0
- data/lib/kiosk/prospector.rb +75 -0
- data/lib/kiosk/resource.rb +313 -0
- data/lib/kiosk/resource_error.rb +5 -0
- data/lib/kiosk/resource_not_found.rb +12 -0
- data/lib/kiosk/resource_uri.rb +82 -0
- data/lib/kiosk/rewrite.rb +13 -0
- data/lib/kiosk/rewrite/cdn_rewrite.rb +9 -0
- data/lib/kiosk/rewrite/node_rewrite.rb +18 -0
- data/lib/kiosk/rewrite/path_rewrite.rb +10 -0
- data/lib/kiosk/rewriter.rb +72 -0
- data/lib/kiosk/searchable.rb +5 -0
- data/lib/kiosk/searchable/resource.rb +15 -0
- data/lib/kiosk/tasks/kiosk.rake +6 -0
- data/lib/kiosk/version.rb +3 -0
- metadata +136 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Kiosk
|
4
|
+
# Adds the ability to declare content rewrites in a controller.
|
5
|
+
#
|
6
|
+
module Controller
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
# Declares a rewrite of content nodes that sets the host portion of
|
11
|
+
# each node's URI attribute (href or src) to target the configured CDN
|
12
|
+
# host.
|
13
|
+
#
|
14
|
+
def rewrite_cdn_paths_for(resource_model)
|
15
|
+
Kiosk.rewriter.add_rewrite(Rewrite.new(:cdn, resource_model))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Declares a rewrite of content nodes.
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# class PostsController
|
23
|
+
# include Kiosk::Controller
|
24
|
+
#
|
25
|
+
# before_filter do
|
26
|
+
# rewrite_content_for(Attachment) do |attachment,node|
|
27
|
+
# case node.name
|
28
|
+
# when 'a'
|
29
|
+
# node['title'] = 'Some photo'
|
30
|
+
# when 'img'
|
31
|
+
# node['src'] = attachment_path(attachment.filename)
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
def rewrite_content_for(resource_model, &blk)
|
38
|
+
Kiosk.rewriter.add_rewrite(Rewrite.new(:node, resource_model, &blk))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Declares a rewrite of content nodes that sets a new URL any href or src
|
42
|
+
# attributes (the first one found, it that order). The given block is
|
43
|
+
# passed the instantiated resource and node as its arguments and should
|
44
|
+
# return the new value for the node attribute.
|
45
|
+
#
|
46
|
+
# Example:
|
47
|
+
#
|
48
|
+
# class PostsController
|
49
|
+
# include Kiosk::Controller
|
50
|
+
#
|
51
|
+
# before_filter do
|
52
|
+
# rewrite_paths_for(Post) { |post| post_path(post.slug) }
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
def rewrite_paths_for(resource_model, &blk)
|
57
|
+
Kiosk.rewriter.add_rewrite(Rewrite.new(:path, resource_model, &blk))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Kiosk
|
2
|
+
module Indexer
|
3
|
+
module Adapter
|
4
|
+
autoload :Base, 'kiosk/indexer/adapter/base'
|
5
|
+
autoload :ThinkingSphinxAdapter, 'kiosk/indexer/adapter/thinking_sphinx_adapter'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def new(type = :thinking_sphinx, *args)
|
9
|
+
"kiosk/indexer/adapter/#{type}_adapter".classify.constantize.new(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Kiosk::Indexer::Adapter
|
4
|
+
class ThinkingSphinxAdapter < Base
|
5
|
+
|
6
|
+
def index(name, io = STDOUT)
|
7
|
+
index = nil
|
8
|
+
|
9
|
+
ThinkingSphinx.context.indexed_resources.each do |res|
|
10
|
+
if i = res.constantize.sphinx_indexes.detect { |index| index.name == name }
|
11
|
+
index = i
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
index.write_xml_to(io) if index
|
16
|
+
end
|
17
|
+
|
18
|
+
module Resource
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
included do
|
22
|
+
# Mixin implementation for Thinking Sphinx
|
23
|
+
unless ThinkingSphinx.context.is_a?(Context)
|
24
|
+
ThinkingSphinx.context.extend(Context)
|
25
|
+
end
|
26
|
+
|
27
|
+
unless ThinkingSphinx::Configuration.instance.configuration.is_a?(Configuration)
|
28
|
+
ThinkingSphinx::Configuration.instance.configuration.extend(Configuration)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
attr_accessor :sphinx_indexes
|
34
|
+
|
35
|
+
def define_index(name, &blk)
|
36
|
+
self.sphinx_indexes ||= []
|
37
|
+
|
38
|
+
ThinkingSphinx.context.add_indexed_resource self
|
39
|
+
|
40
|
+
index = Index.new(self, name.to_s)
|
41
|
+
index.instance_exec(&blk)
|
42
|
+
|
43
|
+
sphinx_indexes << index unless sphinx_indexes.any? { |i| i.name == index.name }
|
44
|
+
end
|
45
|
+
|
46
|
+
def search(*args)
|
47
|
+
options = args.extract_options!
|
48
|
+
query = args.first
|
49
|
+
|
50
|
+
Search.new(query, options).execute_for(self)
|
51
|
+
end
|
52
|
+
|
53
|
+
def sphinx_index_names
|
54
|
+
sphinx_indexes.collect { |index| index.name }
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_riddle
|
58
|
+
if sphinx_indexes
|
59
|
+
sphinx_indexes.collect { |index| index.to_riddle }.flatten
|
60
|
+
else
|
61
|
+
[]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Search
|
68
|
+
attr_reader :query, :options
|
69
|
+
|
70
|
+
def initialize(query, options = {})
|
71
|
+
@query = query
|
72
|
+
@options = {:comment => ''}.merge(options)
|
73
|
+
@proxy = SearchProxy.new(@options)
|
74
|
+
end
|
75
|
+
|
76
|
+
def execute_for(model)
|
77
|
+
query = @proxy.star_query(@query)
|
78
|
+
using_options(@options) do
|
79
|
+
SearchResults.new(client.query(query, index_names_for(model), @options[:comment]), model, @options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def using_options(options)
|
86
|
+
original_options = {}
|
87
|
+
|
88
|
+
options.each do |name,value|
|
89
|
+
if client.respond_to?("#{name}=")
|
90
|
+
original_options[name] = value
|
91
|
+
client.send("#{name}=", value)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
result = yield
|
96
|
+
|
97
|
+
original_options.each do |name,value|
|
98
|
+
client.send("#{name}=", value)
|
99
|
+
end
|
100
|
+
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
def index_names_for(model)
|
105
|
+
model.sphinx_index_names.join(',')
|
106
|
+
end
|
107
|
+
|
108
|
+
def client
|
109
|
+
ThinkingSphinx::Configuration.instance.client
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Used to steal methods from +ThinkingSphinx::Search+.
|
114
|
+
#
|
115
|
+
class SearchProxy < ThinkingSphinx::Search
|
116
|
+
def initialize(options = {})
|
117
|
+
@options = options
|
118
|
+
end
|
119
|
+
|
120
|
+
def star_query(query)
|
121
|
+
@options[:star] ? super(query) : query
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class SearchResults < Array
|
126
|
+
attr_reader :results
|
127
|
+
|
128
|
+
def initialize(sphinx_results, model, search_options)
|
129
|
+
@model = model
|
130
|
+
@results = sphinx_results
|
131
|
+
@options = search_options
|
132
|
+
|
133
|
+
populate if @results && @results[:matches]
|
134
|
+
end
|
135
|
+
|
136
|
+
def current_page
|
137
|
+
@options[:page].blank? ? 1 : @options[:page].to_i
|
138
|
+
end
|
139
|
+
|
140
|
+
def per_page
|
141
|
+
(@options[:per_page] || 20).to_i
|
142
|
+
end
|
143
|
+
|
144
|
+
def total_entries
|
145
|
+
@results[:total_found] || 0
|
146
|
+
end
|
147
|
+
|
148
|
+
def total_pages
|
149
|
+
(@results[:total] / per_page.to_f).ceil
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def populate
|
155
|
+
replace(@results[:matches].collect { |match|
|
156
|
+
begin
|
157
|
+
@model.find(match[:doc])
|
158
|
+
rescue Kiosk::ResourceNotFound => e
|
159
|
+
@model.new
|
160
|
+
end
|
161
|
+
})
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
module Context
|
166
|
+
attr_reader :indexed_resources
|
167
|
+
|
168
|
+
def add_indexed_resource(resource)
|
169
|
+
resource = resource.name if resource.is_a?(Class)
|
170
|
+
|
171
|
+
@indexed_resources ||= []
|
172
|
+
@indexed_resources << resource unless @indexed_resources.include?(resource)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
module Configuration
|
177
|
+
# Reimplement +Riddle::Configuration#render+ to include our own indexes
|
178
|
+
# in the config before it's rendered.
|
179
|
+
#
|
180
|
+
def render
|
181
|
+
ThinkingSphinx.context.indexed_resources.each do |resource|
|
182
|
+
resource.constantize.to_riddle.each do |index|
|
183
|
+
indices << index unless indices.any? { |i| i.name == index.name }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
super
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class Index
|
192
|
+
attr_reader :mode, :name
|
193
|
+
|
194
|
+
def initialize(model, name)
|
195
|
+
@model = model
|
196
|
+
@name = name
|
197
|
+
|
198
|
+
@fields = []
|
199
|
+
end
|
200
|
+
|
201
|
+
def indexes(*fields)
|
202
|
+
@fields += fields.collect { |field| Field.new(field) }
|
203
|
+
end
|
204
|
+
|
205
|
+
def config
|
206
|
+
@config ||= ThinkingSphinx::Configuration.instance
|
207
|
+
end
|
208
|
+
|
209
|
+
def to_riddle
|
210
|
+
index = Riddle::Configuration::Index.new(@name)
|
211
|
+
index.path = File.join(config.searchd_file_path, index.name)
|
212
|
+
|
213
|
+
config.index_options.each do |key,value|
|
214
|
+
method = "#{key}=".to_sym
|
215
|
+
index.send(method, value) if index.respond_to?(method)
|
216
|
+
end
|
217
|
+
|
218
|
+
source = Riddle::Configuration::XMLSource.new("#{@name}_source", :xmlpipe2)
|
219
|
+
source.xmlpipe_command = rake("kiosk:index[#{name}]")
|
220
|
+
|
221
|
+
index.sources << source
|
222
|
+
|
223
|
+
[index]
|
224
|
+
end
|
225
|
+
|
226
|
+
def write_xml_to(io)
|
227
|
+
xm = Builder::XmlMarkup.new(:target => io)
|
228
|
+
|
229
|
+
xm.instruct!
|
230
|
+
xm.sphinx :docset, 'xmlns:sphinx' => 'http://sphinxsearch.com' do
|
231
|
+
xm.sphinx :schema do
|
232
|
+
@fields.each { |field| field.to_xml(xm) }
|
233
|
+
end
|
234
|
+
|
235
|
+
@model.all.each do |resource|
|
236
|
+
xm.sphinx :document, :id => resource.id do
|
237
|
+
@fields.each do |field|
|
238
|
+
xm.send(field.name, resource.send(field.name))
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def rake(task)
|
248
|
+
"#{Gem.bin_path('rake', 'rake')} --silent -f #{Rails.root}/Rakefile #{task}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class Field
|
253
|
+
attr_reader :name
|
254
|
+
|
255
|
+
def initialize(name)
|
256
|
+
name = name.to_s
|
257
|
+
raise ArgumentError.new('invalid name') unless name =~ /^\w+$/
|
258
|
+
@name = name
|
259
|
+
end
|
260
|
+
|
261
|
+
def to_xml(builder)
|
262
|
+
builder.sphinx :field, :name => name
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Kiosk
|
4
|
+
# Provides integration between I18n and WordPress for automatic retrieval of
|
5
|
+
# content translated to the language of the user session. This module
|
6
|
+
# depends on the installation of the WPML-JSON-API WordPress plugin.
|
7
|
+
#
|
8
|
+
module Localizable::Resource
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
attr_accessor :locale_scope_stack
|
13
|
+
|
14
|
+
# Executes the given block within a scope that limits found content
|
15
|
+
# resources to the given locale.
|
16
|
+
#
|
17
|
+
def with_locale(locale, &blk)
|
18
|
+
with_parameters(:language => locale, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Executes the given block within a scope that translates found content
|
22
|
+
# resources to the given locale. See +Resource.with_params+ for details
|
23
|
+
# on scope execution and inheritance.
|
24
|
+
#
|
25
|
+
def localized_to(locale, &blk)
|
26
|
+
with_parameters(:to_language => locale, &blk)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
|
3
|
+
module Kiosk
|
4
|
+
# Provides an +ActionController+ filter that scopes all content resource
|
5
|
+
# calls to only fetch content for the default locale and localize it all to
|
6
|
+
# the current request locale.
|
7
|
+
#
|
8
|
+
module Localizer
|
9
|
+
def self.around(controller)
|
10
|
+
Resource.with_locale(Kiosk.origin.default_locale || I18n.default_locale) do
|
11
|
+
Resource.localized_to(I18n.locale) do
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/kiosk/origin.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Kiosk
|
2
|
+
class Origin
|
3
|
+
attr_reader :site, :indexer, :default_locale, :cdn
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@site = (config['site'] || '').sub(/\/+$/, '') + '/'
|
7
|
+
@indexer = Kiosk::Indexer::Adapter.new(config['indexer']) if config['indexer']
|
8
|
+
@default_locale = config['default_locale']
|
9
|
+
@cdn = Kiosk::Cdn.new(config['cdn'] || {})
|
10
|
+
end
|
11
|
+
|
12
|
+
def searcher
|
13
|
+
@indexer
|
14
|
+
end
|
15
|
+
|
16
|
+
def site_uri
|
17
|
+
URI.parse(site)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|