toto-bongo 1.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/.document +5 -0
- data/LICENSE +23 -0
- data/README.md +186 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/lib/ext/ext.rb +46 -0
- data/lib/toto-bongo.rb +543 -0
- data/test/articles/1900-05-17-the-wonderful-wizard-of-oz.txt +5 -0
- data/test/articles/2001-01-01-two-thousand-and-one.txt +5 -0
- data/test/articles/2009-04-01-tilt-factor.txt +5 -0
- data/test/articles/2009-12-04-some-random-article.txt +5 -0
- data/test/articles/2009-12-11-the-dichotomy-of-design.txt +5 -0
- data/test/autotest.rb +34 -0
- data/test/templates/about.html.haml +0 -0
- data/test/templates/archives.html.haml +7 -0
- data/test/templates/article.html.haml +16 -0
- data/test/templates/feed.builder +21 -0
- data/test/templates/index.builder +21 -0
- data/test/templates/index.html.haml +14 -0
- data/test/templates/layout.html.haml +14 -0
- data/test/test_helper.rb +44 -0
- data/test/toto_test.rb +285 -0
- data/toto-bongo.gemspec +75 -0
- metadata +140 -0
data/lib/toto-bongo.rb
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'haml'
|
|
4
|
+
require 'rack'
|
|
5
|
+
require 'digest'
|
|
6
|
+
require 'open-uri'
|
|
7
|
+
require 'RedCloth'
|
|
8
|
+
require 'builder'
|
|
9
|
+
require 'logger'
|
|
10
|
+
$:.unshift File.dirname(__FILE__)
|
|
11
|
+
|
|
12
|
+
require 'ext/ext'
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# TotoBongo
|
|
16
|
+
#
|
|
17
|
+
# TotoBongo Consits of the following
|
|
18
|
+
#
|
|
19
|
+
# Module TotoBongo
|
|
20
|
+
# encapsulates the app
|
|
21
|
+
#
|
|
22
|
+
# Module Template
|
|
23
|
+
# Handles the conversions to html
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
module TotoBongo
|
|
27
|
+
|
|
28
|
+
#default paths
|
|
29
|
+
Paths = {
|
|
30
|
+
:templates => "templates",
|
|
31
|
+
:pages => "templates/pages",
|
|
32
|
+
:articles => "articles"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def self.env
|
|
36
|
+
ENV['RACK_ENV'] || 'production'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.env= env
|
|
40
|
+
ENV['RACK_ENV'] = env
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
attr_accessor :logger
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@logger = Logger.new(STDOUT)
|
|
48
|
+
@logger.level = Logger::WARN
|
|
49
|
+
|
|
50
|
+
#set logger for debug
|
|
51
|
+
if(ENV['TOTODEBUG'])
|
|
52
|
+
@logger.level = Logger::DEBUG
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
# Handles all templating options
|
|
57
|
+
# Is responsible for:
|
|
58
|
+
# 1. Calling the Haml engine on pages to render them to html
|
|
59
|
+
# 2. Calling the Textile engine on textile text to render them to html
|
|
60
|
+
# 3. Registering All the classes at initialization
|
|
61
|
+
#
|
|
62
|
+
module Template
|
|
63
|
+
#
|
|
64
|
+
# This will call Haml render
|
|
65
|
+
# Call the config block to make convert the
|
|
66
|
+
# page to html
|
|
67
|
+
#
|
|
68
|
+
#
|
|
69
|
+
def to_html page, config, &blk
|
|
70
|
+
TotoBongo::logger.debug("Called Template::to_html")
|
|
71
|
+
path = ([:layout, :repo].include?(page) ? Paths[:templates] : Paths[:pages])
|
|
72
|
+
result = config[:to_html].call(path, page, binding)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
#Converst a textile text into html
|
|
77
|
+
#
|
|
78
|
+
def textile text
|
|
79
|
+
TotoBongo::logger.debug("Called Template::Textile")
|
|
80
|
+
RedCloth.new(text.to_s.strip).to_html
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Intercept any method missing
|
|
85
|
+
#
|
|
86
|
+
def method_missing m, *args, &blk
|
|
87
|
+
TotoBongo::logger.debug("Called method_missing: method = #{method_missin}")
|
|
88
|
+
self.keys.include?(m) ? self[m] : super
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# define the following methods during initialization
|
|
93
|
+
# TotoBongo::Site::Context
|
|
94
|
+
# TotoBongo::Repo
|
|
95
|
+
# TotoBongo::Archives
|
|
96
|
+
# TotoBongo::Article
|
|
97
|
+
#
|
|
98
|
+
def self.included obj
|
|
99
|
+
TotoBongo::logger.debug("Called Template::include: obj = #{obj}")
|
|
100
|
+
obj.class_eval do
|
|
101
|
+
define_method(obj.to_s.split('::').last.downcase) { self }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end #Template
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Site
|
|
110
|
+
# Is responsible for handling the site
|
|
111
|
+
# It has handles the
|
|
112
|
+
#
|
|
113
|
+
#
|
|
114
|
+
class Site
|
|
115
|
+
|
|
116
|
+
def initialize config
|
|
117
|
+
TotoBongo::logger.debug("Called Site::initialize")
|
|
118
|
+
@config = config
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def [] *args
|
|
122
|
+
TotoBongo::logger.debug("Called Site::[]: args = #{args}")
|
|
123
|
+
@config[*args]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def []= key, value
|
|
127
|
+
TotoBongo::logger.debug("Called Site::[]=: key = #{key} value=#{value}")
|
|
128
|
+
@config.set key, value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
#Called when index is requested
|
|
132
|
+
#
|
|
133
|
+
def index type = :html
|
|
134
|
+
TotoBongo::logger.debug("Called Site::index")
|
|
135
|
+
articles = type == :html ? self.articles.reverse : self.articles
|
|
136
|
+
#know initialize the articles
|
|
137
|
+
{:articles => articles.map do |article|
|
|
138
|
+
Article.new article, @config
|
|
139
|
+
end}.merge archives
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
#
|
|
143
|
+
# Makes the list's of all articles
|
|
144
|
+
# pages/archives.html.haml with access to :archives to generate the full list of posts
|
|
145
|
+
#
|
|
146
|
+
def archives filter = ""
|
|
147
|
+
TotoBongo::logger.debug("Called Site::archive ")
|
|
148
|
+
entries = ! self.articles.empty??
|
|
149
|
+
self.articles.select do |a|
|
|
150
|
+
filter !~ /^\d{4}/ || File.basename(a) =~ /^#{filter}/
|
|
151
|
+
end.reverse.map do |article|
|
|
152
|
+
Article.new article, @config
|
|
153
|
+
end : []
|
|
154
|
+
return :archives => Archives.new(entries, @config)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def article route
|
|
158
|
+
TotoBongo::logger.debug("Called Site::article ")
|
|
159
|
+
Article.new("#{Paths[:articles]}/#{route.join('-')}.#{self[:ext]}", @config).load
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# called when the user requests a /
|
|
163
|
+
# Returns whatever the site index config is
|
|
164
|
+
# default is "index"
|
|
165
|
+
def /
|
|
166
|
+
TotoBongo::logger.debug("Called Site::/ ")
|
|
167
|
+
self[:root]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
#
|
|
171
|
+
# Called by the server after the route and the mime type are
|
|
172
|
+
# taken from the request,
|
|
173
|
+
#
|
|
174
|
+
# This is the first function that is called from
|
|
175
|
+
# the server.
|
|
176
|
+
# It should return the html to render
|
|
177
|
+
#
|
|
178
|
+
def go route, env = {}, type = :html
|
|
179
|
+
TotoBongo::logger.debug("Called Site::go ")
|
|
180
|
+
#check if the request includes an specific route
|
|
181
|
+
#else call / to get the index
|
|
182
|
+
route << self./ if route.empty?
|
|
183
|
+
|
|
184
|
+
type, path = type =~ /html|xml|json/ ? type.to_sym : :html, route.join('/')
|
|
185
|
+
context = lambda do |data, page|
|
|
186
|
+
Context.new(data, @config, path, env).render(page, type)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
body, status = if Context.new.respond_to?(:"to_#{type}")
|
|
190
|
+
|
|
191
|
+
if route.first =~ /\d{4}/
|
|
192
|
+
case route.size
|
|
193
|
+
when 1..3
|
|
194
|
+
context[archives(route * '-'), :archives]
|
|
195
|
+
when 4
|
|
196
|
+
context[article(route), :article]
|
|
197
|
+
else
|
|
198
|
+
puts "400"
|
|
199
|
+
http 400
|
|
200
|
+
end #end case
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# Responde to a path, when the request is for example index
|
|
204
|
+
elsif respond_to?(path)
|
|
205
|
+
#call the path, it will return the HTML of the path
|
|
206
|
+
context[send(path, type), path.to_sym]
|
|
207
|
+
else
|
|
208
|
+
context[{}, path.to_sym]
|
|
209
|
+
end
|
|
210
|
+
else
|
|
211
|
+
http 400
|
|
212
|
+
end #end context new respond
|
|
213
|
+
|
|
214
|
+
return body, status
|
|
215
|
+
|
|
216
|
+
rescue Errno::ENOENT => e
|
|
217
|
+
TotoBongo::logger.info("Errno:ENOENT: #{e.message} ")
|
|
218
|
+
return :body => http(404).first, :type => :html, :status => 404
|
|
219
|
+
else
|
|
220
|
+
TotoBongo::logger.debug("Status set 200 OK")
|
|
221
|
+
return :body => body || "", :type => type, :status => status || 200
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
protected
|
|
227
|
+
|
|
228
|
+
#sets the error code
|
|
229
|
+
def http code
|
|
230
|
+
TotoBongo::logger.debug("http with code #{code}")
|
|
231
|
+
[@config[:error].call(code), code]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# return a path to an article
|
|
235
|
+
def articles
|
|
236
|
+
TotoBongo::logger.debug("articles")
|
|
237
|
+
self.class.articles self[:ext]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
#
|
|
241
|
+
#Returns the path to an article based on an extension
|
|
242
|
+
#Default ext is .txt
|
|
243
|
+
def self.articles ext
|
|
244
|
+
TotoBongo::logger.debug("self.articles")
|
|
245
|
+
Dir["#{Paths[:articles]}/*.#{ext}"].sort_by {|entry| File.basename(entry) }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
#
|
|
250
|
+
# This class holds all the context to set the scope during rendering
|
|
251
|
+
# The context has access to the config and the article
|
|
252
|
+
# and defines all the article and archive method
|
|
253
|
+
#
|
|
254
|
+
class Context
|
|
255
|
+
include Template
|
|
256
|
+
attr_reader :env
|
|
257
|
+
|
|
258
|
+
def initialize ctx = {}, config = {}, path = "/", env = {}
|
|
259
|
+
TotoBongo::logger.debug("Initialize context")
|
|
260
|
+
@config, @context, @path, @env = config, ctx, path, env
|
|
261
|
+
#for each article, initialize an article object
|
|
262
|
+
@articles = Site.articles(@config[:ext]).reverse.map do |a|
|
|
263
|
+
Article.new(a, @config)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
ctx.each do |k, v|
|
|
267
|
+
meta_def(k) { ctx.instance_of?(Hash) ? v : ctx.send(k) }
|
|
268
|
+
end
|
|
269
|
+
TotoBongo::logger.debug("End of initialize context")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def title
|
|
273
|
+
TotoBongo::logger.debug("Context::title")
|
|
274
|
+
@config[:title]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def description
|
|
278
|
+
TotoBongo::logger.debug("Context::desciption")
|
|
279
|
+
@config[:description]
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def keywords
|
|
283
|
+
TotoBongo::logger.debug("Context::keywords")
|
|
284
|
+
@config[:keywords]
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def render page, type
|
|
289
|
+
TotoBongo::logger.debug("Context::render")
|
|
290
|
+
content = to_html page, @config
|
|
291
|
+
type == :html ? to_html(:layout, @config, &Proc.new { content }) : send(:"to_#{type}", page)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def to_xml page
|
|
295
|
+
TotoBongo::logger.debug("Context::to_xml")
|
|
296
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
|
297
|
+
instance_eval File.read("#{Paths[:templates]}/#{page}.builder")
|
|
298
|
+
end
|
|
299
|
+
alias :to_atom to_xml
|
|
300
|
+
|
|
301
|
+
def method_missing m, *args, &blk
|
|
302
|
+
TotoBongo::logger.debug("Context::missing_method #{m}")
|
|
303
|
+
@context.respond_to?(m) ? @context.send(m, *args, &blk) : super
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
end #end class contex
|
|
307
|
+
|
|
308
|
+
end #End site
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class Archives < Array
|
|
312
|
+
include Template
|
|
313
|
+
|
|
314
|
+
def initialize articles, config
|
|
315
|
+
TotoBongo::logger.debug("Archives::initialize")
|
|
316
|
+
self.replace articles
|
|
317
|
+
@config = config
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def [] a
|
|
321
|
+
TotoBongo::logger.debug("Archives::[]: a = #{a}")
|
|
322
|
+
a.is_a?(Range) ? self.class.new(self.slice(a) || [], @config) : super
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def to_html
|
|
326
|
+
TotoBongo::logger.debug("Archives::to_html")
|
|
327
|
+
super(:archives, @config)
|
|
328
|
+
end
|
|
329
|
+
alias :to_s to_html
|
|
330
|
+
alias :archive archives
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class Article < Hash
|
|
336
|
+
include Template
|
|
337
|
+
|
|
338
|
+
def initialize obj, config = {}
|
|
339
|
+
TotoBongo::logger.debug("Article::initialize")
|
|
340
|
+
@obj, @config = obj, config
|
|
341
|
+
self.load if obj.is_a? Hash
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def load
|
|
346
|
+
TotoBongo::logger.debug("Article::load")
|
|
347
|
+
data = if @obj.is_a? String
|
|
348
|
+
meta, self[:body] = File.read(@obj).split(/\n\n/, 2)
|
|
349
|
+
|
|
350
|
+
# use the date from the filename, or else toto won't find the article
|
|
351
|
+
@obj =~ /\/(\d{4}-\d{2}-\d{2})[^\/]*$/
|
|
352
|
+
($1 ? {:date => $1} : {}).merge(YAML.load(meta))
|
|
353
|
+
elsif @obj.is_a? Hash
|
|
354
|
+
@obj
|
|
355
|
+
end.inject({}) {|h, (k,v)| h.merge(k.to_sym => v) }
|
|
356
|
+
|
|
357
|
+
self.taint
|
|
358
|
+
self.update data
|
|
359
|
+
self[:date] = Date.parse(self[:date].gsub('/', '-')) rescue Date.today
|
|
360
|
+
self
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
#
|
|
364
|
+
# Called by path when constructing the SEO url
|
|
365
|
+
#
|
|
366
|
+
def [] key
|
|
367
|
+
TotoBongo::logger.debug("Article::key: key = #{key}")
|
|
368
|
+
|
|
369
|
+
self.load unless self.tainted?
|
|
370
|
+
super
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def slug
|
|
374
|
+
TotoBongo::logger.debug("Article::slug")
|
|
375
|
+
self[:slug] || self[:title].slugize
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
#create a small summary of the body.
|
|
379
|
+
# Defaulsts 150 characters
|
|
380
|
+
def summary length = nil
|
|
381
|
+
TotoBongo::logger.debug("Article::summary")
|
|
382
|
+
config = @config[:summary]
|
|
383
|
+
sum = if self[:body] =~ config[:delim]
|
|
384
|
+
self[:body].split(config[:delim]).first
|
|
385
|
+
else
|
|
386
|
+
self[:body].match(/(.{1,#{length || config[:length] || config[:max]}}.*?)(\n|\Z)/m).to_s
|
|
387
|
+
end
|
|
388
|
+
textile(sum.length == self[:body].length ? sum : sum.strip.sub(/\.\Z/, '…'))
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def url
|
|
392
|
+
TotoBongo::logger.debug("Article::url")
|
|
393
|
+
"http://#{(@config[:url].sub("http://", '') + self.path).squeeze('/')}"
|
|
394
|
+
end
|
|
395
|
+
alias :permalink url
|
|
396
|
+
|
|
397
|
+
def body
|
|
398
|
+
TotoBongo::logger.debug("Article::body")
|
|
399
|
+
textile self[:body].sub(@config[:summary][:delim], '') rescue textile self[:body]
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
#Path returns a SEO friendly URL path
|
|
403
|
+
# Eg for blog/articles/1900-05-17-the-wonderful-wizard-of-oz.txt
|
|
404
|
+
# it returns /blog/1900/05/17/the-wonderful-wizard-of-oz/
|
|
405
|
+
def path
|
|
406
|
+
TotoBongo::logger.debug("Article::path")
|
|
407
|
+
"/#{@config[:prefix]}#{self[:date].strftime("/%Y/%m/%d/#{slug}/")}".squeeze('/')
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def title()
|
|
411
|
+
TotoBongo::logger.debug("Article::title")
|
|
412
|
+
self[:title] || "an article"
|
|
413
|
+
end
|
|
414
|
+
def date()
|
|
415
|
+
TotoBongo::logger.debug("Article::path")
|
|
416
|
+
@config[:date].call(self[:date])
|
|
417
|
+
|
|
418
|
+
end
|
|
419
|
+
def author()
|
|
420
|
+
TotoBongo::logger.debug("Article::path")
|
|
421
|
+
self[:author] || @config[:author]
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def description()
|
|
425
|
+
TotoBongo::logger.debug("Article::path")
|
|
426
|
+
self[:description] || title()
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def keywords()
|
|
430
|
+
TotoBongo::logger.debug("Article::keywords")
|
|
431
|
+
self[:keywords] || title()
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def to_html()
|
|
436
|
+
TotoBongo::logger.debug("Article::path")
|
|
437
|
+
self.load; super(:article, @config)
|
|
438
|
+
end
|
|
439
|
+
alias :to_s to_html
|
|
440
|
+
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class Config < Hash
|
|
447
|
+
|
|
448
|
+
#
|
|
449
|
+
#This is the hash that stores all teh configuation options
|
|
450
|
+
#
|
|
451
|
+
|
|
452
|
+
Defaults = {
|
|
453
|
+
:author => ENV['USER'], # blog author
|
|
454
|
+
:title => Dir.pwd.split('/').last, # blog index title
|
|
455
|
+
:description => "Blog for your existing rails app", # blog meta description
|
|
456
|
+
:keywords => "blog rails existing", # blog meta keywords
|
|
457
|
+
:root => "index", # site index
|
|
458
|
+
:url => "http://127.0.0.1", # root URL of the site
|
|
459
|
+
:prefix => "blog", # common path prefix for the blog
|
|
460
|
+
:date => lambda {|now| now.strftime("%d/%m/%Y") }, # date function
|
|
461
|
+
:disqus => false, # disqus name
|
|
462
|
+
:summary => {:max => 150, :delim => /~\n/}, # length of summary and delimiter
|
|
463
|
+
:ext => "txt", # extension for articles
|
|
464
|
+
:cache => 28800, # cache duration (seconds)
|
|
465
|
+
:to_html => lambda {|path, page, ctx| # returns an html, from a path & context
|
|
466
|
+
Haml::Engine.new(File.read("#{path}/#{page}.html.haml")).render(ctx)
|
|
467
|
+
},
|
|
468
|
+
:error => lambda {|code| # The HTML for your error page
|
|
469
|
+
"<font style='font-size:300%'>toto-bongo error (#{code})</font>"
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def initialize obj
|
|
475
|
+
|
|
476
|
+
self.update Defaults
|
|
477
|
+
self.update obj
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def set key, val = nil, &blk
|
|
481
|
+
if val.is_a? Hash
|
|
482
|
+
self[key].update val
|
|
483
|
+
else
|
|
484
|
+
self[key] = block_given?? blk : val
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
# The HTTP server
|
|
495
|
+
class Server
|
|
496
|
+
attr_reader :config, :site
|
|
497
|
+
|
|
498
|
+
def initialize config = {}, &blk
|
|
499
|
+
@config = config.is_a?(Config) ? config : Config.new(config)
|
|
500
|
+
@config.instance_eval(&blk) if block_given?
|
|
501
|
+
@site = TotoBongo::Site.new(@config)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
#
|
|
506
|
+
# This is the entry point of the request
|
|
507
|
+
# On each request, this is the first method that gets
|
|
508
|
+
# called
|
|
509
|
+
#
|
|
510
|
+
def call env
|
|
511
|
+
TotoBongo::logger.debug("***************REQUEST BEGIN*************")
|
|
512
|
+
@request = Rack::Request.new env
|
|
513
|
+
@response = Rack::Response.new
|
|
514
|
+
return [400, {}, []] unless @request.get?
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
#puts "Request path info is: #{@request.path_info}"
|
|
518
|
+
|
|
519
|
+
path, mime = @request.path_info.split('.')
|
|
520
|
+
route = (path || '/').split('/').reject {|i| i.empty? }
|
|
521
|
+
|
|
522
|
+
response = @site.go(route, env, *(mime ? mime : []))
|
|
523
|
+
|
|
524
|
+
@response.body = [response[:body]]
|
|
525
|
+
@response['Content-Length'] = response[:body].length.to_s unless response[:body].empty?
|
|
526
|
+
@response['Content-Type'] = Rack::Mime.mime_type(".#{response[:type]}")
|
|
527
|
+
|
|
528
|
+
# Set http cache headers
|
|
529
|
+
@response['Cache-Control'] = if TotoBongo.env == 'production'
|
|
530
|
+
"public, max-age=#{@config[:cache]}"
|
|
531
|
+
else
|
|
532
|
+
"no-cache, must-revalidate"
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
@response['ETag'] = %("#{Digest::SHA1.hexdigest(response[:body])}")
|
|
536
|
+
TotoBongo::logger.debug("****************REQUEST END******************")
|
|
537
|
+
|
|
538
|
+
@response.status = response[:status]
|
|
539
|
+
@response.finish
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
data/test/autotest.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Convenience Methods
|
|
3
|
+
#
|
|
4
|
+
def run(cmd)
|
|
5
|
+
print "\n\n"
|
|
6
|
+
puts(cmd)
|
|
7
|
+
system(cmd)
|
|
8
|
+
print "\n\n"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run_all_tests
|
|
12
|
+
# see Rakefile for the definition of the test:all task
|
|
13
|
+
system("rake -s test:all VERBOSE=true")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# Watchr Rules
|
|
18
|
+
#
|
|
19
|
+
watch('^test/.*?_test\.rb' ) {|m| run("ruby -rubygems %s" % m[0]) }
|
|
20
|
+
watch('^lib/(.*)\.rb' ) {|m| run("ruby -rubygems test/%s_test.rb" % m[1]) }
|
|
21
|
+
watch('^lib/toto/(.*)\.rb' ) {|m| run("ruby -rubygems test/%s_test.rb" % m[1]) }
|
|
22
|
+
watch('^test/test_helper\.rb') { run_all_tests }
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Signal Handling
|
|
26
|
+
#
|
|
27
|
+
# Ctrl-\
|
|
28
|
+
Signal.trap('QUIT') do
|
|
29
|
+
puts " --- Running all tests ---\n\n"
|
|
30
|
+
run_all_tests
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Ctrl-C
|
|
34
|
+
Signal.trap('INT') { abort("\n") }
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
%article.post
|
|
2
|
+
%header
|
|
3
|
+
%h1= title
|
|
4
|
+
%span.date= date
|
|
5
|
+
%p= author
|
|
6
|
+
%section.content
|
|
7
|
+
= body
|
|
8
|
+
%section.comments
|
|
9
|
+
- if @config[:disqus]
|
|
10
|
+
#disqus_thread
|
|
11
|
+
%script{:src => "http://disqus.com/forums/#{@config[:disqus]}/embed.js", :type => "text/javascript"} %noscript
|
|
12
|
+
%a{:href => "http://#{@config[:disqus]}.disqus.com/?url=ref"} View the discussion thread.
|
|
13
|
+
%a.dsq-brlink{:href => "http://disqus.com"}
|
|
14
|
+
blog comments powered by
|
|
15
|
+
%span.logo-disqus Disqus
|
|
16
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
xml.instruct!
|
|
2
|
+
xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
|
|
3
|
+
xml.title @config[:title]
|
|
4
|
+
xml.id @config[:url]
|
|
5
|
+
xml.updated articles.first[:date].iso8601 unless articles.empty?
|
|
6
|
+
xml.author { xml.name @config[:author] }
|
|
7
|
+
|
|
8
|
+
articles.each do |article|
|
|
9
|
+
xml.entry do
|
|
10
|
+
xml.title article.title
|
|
11
|
+
xml.link "rel" => "alternate", "href" => article.url
|
|
12
|
+
xml.id article.url
|
|
13
|
+
xml.published article[:date].iso8601
|
|
14
|
+
xml.updated article[:date].iso8601
|
|
15
|
+
xml.author { xml.name @config[:author] }
|
|
16
|
+
xml.summary article.summary, "type" => "html"
|
|
17
|
+
xml.content article.body, "type" => "html"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
xml.instruct!
|
|
2
|
+
xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
|
|
3
|
+
xml.title @config[:title]
|
|
4
|
+
xml.id @config[:url]
|
|
5
|
+
xml.updated articles.first[:date].iso8601 unless articles.empty?
|
|
6
|
+
xml.author { xml.name @config[:author] }
|
|
7
|
+
|
|
8
|
+
articles.reverse[0...10].each do |article|
|
|
9
|
+
xml.entry do
|
|
10
|
+
xml.title article.title
|
|
11
|
+
xml.link "rel" => "alternate", "href" => article.url
|
|
12
|
+
xml.id article.url
|
|
13
|
+
xml.published article[:date].iso8601
|
|
14
|
+
xml.updated article[:date].iso8601
|
|
15
|
+
xml.author { xml.name @config[:author] }
|
|
16
|
+
xml.summary article.summary, "type" => "html"
|
|
17
|
+
xml.content article.body, "type" => "html"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
%section#articles
|
|
2
|
+
- for article in articles[0...3]
|
|
3
|
+
%article.post
|
|
4
|
+
%header
|
|
5
|
+
%h1
|
|
6
|
+
%a{:href => article.path}= article.title
|
|
7
|
+
%span.date= article.date
|
|
8
|
+
%section.content
|
|
9
|
+
= article.summary
|
|
10
|
+
.more
|
|
11
|
+
%a{:href => article.path} read on »
|
|
12
|
+
%section#archives
|
|
13
|
+
= archives[3..-1]
|
|
14
|
+
|