kiosk 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|