ceilingfish-toto 0.3.6 → 0.3.7
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/Rakefile +1 -0
- data/VERSION +1 -1
- data/ceilingfish-toto.gemspec +4 -2
- data/lib/ext/ext.rb +36 -0
- data/lib/toto.rb +351 -0
- metadata +4 -2
data/Rakefile
CHANGED
|
@@ -10,6 +10,7 @@ begin
|
|
|
10
10
|
gem.email = "ceilingfish@gmail.com"
|
|
11
11
|
gem.homepage = "http://github.com/ceilingfish/toto"
|
|
12
12
|
gem.authors = ["cloudhead", "ceilingfish"]
|
|
13
|
+
# gem.files = FileList["{bin,lib}/**/*"]
|
|
13
14
|
gem.add_development_dependency "riot"
|
|
14
15
|
gem.add_dependency "builder"
|
|
15
16
|
gem.add_dependency "rack"
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.7
|
data/ceilingfish-toto.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{ceilingfish-toto}
|
|
8
|
-
s.version = "0.3.
|
|
8
|
+
s.version = "0.3.7"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["cloudhead", "ceilingfish"]
|
|
12
|
-
s.date = %q{2010-02-
|
|
12
|
+
s.date = %q{2010-02-15}
|
|
13
13
|
s.description = %q{the tiniest blog-engine in Oz.}
|
|
14
14
|
s.email = %q{ceilingfish@gmail.com}
|
|
15
15
|
s.extra_rdoc_files = [
|
|
@@ -24,6 +24,8 @@ Gem::Specification.new do |s|
|
|
|
24
24
|
"Rakefile",
|
|
25
25
|
"VERSION",
|
|
26
26
|
"ceilingfish-toto.gemspec",
|
|
27
|
+
"lib/ext/ext.rb",
|
|
28
|
+
"lib/toto.rb",
|
|
27
29
|
"test/articles/1900-05-17-the-wonderful-wizard-of-oz.txt",
|
|
28
30
|
"test/articles/2001-01-01-two-thousand-and-one.txt",
|
|
29
31
|
"test/articles/2009-04-01-tilt-factor.txt",
|
data/lib/ext/ext.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class Object
|
|
2
|
+
def meta_def name, &blk
|
|
3
|
+
(class << self; self; end).instance_eval do
|
|
4
|
+
define_method(name, &blk)
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class String
|
|
10
|
+
def slugize
|
|
11
|
+
self.downcase.gsub(/&/, 'and').gsub(/\s+/, '-').gsub(/[^a-z0-9-]/, '')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def humanize
|
|
15
|
+
self.capitalize.gsub(/[-_]+/, ' ')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Fixnum
|
|
20
|
+
def ordinal
|
|
21
|
+
# 1 => 1st
|
|
22
|
+
# 2 => 2nd
|
|
23
|
+
# 3 => 3rd
|
|
24
|
+
# ...
|
|
25
|
+
case self % 100
|
|
26
|
+
when 11..13; "#{self}th"
|
|
27
|
+
else
|
|
28
|
+
case self % 10
|
|
29
|
+
when 1; "#{self}st"
|
|
30
|
+
when 2; "#{self}nd"
|
|
31
|
+
when 3; "#{self}rd"
|
|
32
|
+
else "#{self}th"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/toto.rb
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'time'
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'rack'
|
|
5
|
+
require 'digest'
|
|
6
|
+
require 'open-uri'
|
|
7
|
+
|
|
8
|
+
require 'rdiscount'
|
|
9
|
+
require 'builder'
|
|
10
|
+
|
|
11
|
+
$:.unshift File.dirname(__FILE__)
|
|
12
|
+
|
|
13
|
+
require 'ext/ext'
|
|
14
|
+
|
|
15
|
+
module Toto
|
|
16
|
+
Paths = {
|
|
17
|
+
:templates => "templates",
|
|
18
|
+
:pages => "templates/pages",
|
|
19
|
+
:articles => "articles"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def self.env
|
|
23
|
+
ENV['RACK_ENV'] || 'production'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.env= env
|
|
27
|
+
ENV['RACK_ENV'] = env
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module Template
|
|
31
|
+
def to_html page, &blk
|
|
32
|
+
path = ([:layout, :repo].include?(page) ? Paths[:templates] : Paths[:pages])
|
|
33
|
+
ERB.new(File.read("#{path}/#{page}.rhtml")).result(binding)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def markdown text
|
|
37
|
+
if (options = @config[:markdown])
|
|
38
|
+
Markdown.new(text.to_s.strip, *(options.eql?(true) ? [] : options)).to_html
|
|
39
|
+
else
|
|
40
|
+
text.strip
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def method_missing m, *args, &blk
|
|
45
|
+
self.keys.include?(m) ? self[m] : super
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.included obj
|
|
49
|
+
obj.class_eval do
|
|
50
|
+
define_method(obj.to_s.split('::').last.downcase) { self }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module ConfigHelpers
|
|
56
|
+
def [] *args
|
|
57
|
+
@config[*args]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def []= key, value
|
|
61
|
+
@config.set key, value
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
module PageHelpers
|
|
66
|
+
include ConfigHelpers
|
|
67
|
+
|
|
68
|
+
def archives filter = ""
|
|
69
|
+
entries = ! self.articles.empty??
|
|
70
|
+
self.articles.select do |a|
|
|
71
|
+
filter !~ /^\d{4}/ || File.basename(a) =~ /^#{filter}/
|
|
72
|
+
end.reverse.map do |article|
|
|
73
|
+
Article.new File.new(article), @config
|
|
74
|
+
end : []
|
|
75
|
+
|
|
76
|
+
return Archives.new(entries)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def title
|
|
80
|
+
self[:title]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def articles ext = self[:ext]
|
|
84
|
+
Dir["#{Paths[:articles]}/*.#{ext}"]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Site
|
|
89
|
+
include PageHelpers
|
|
90
|
+
|
|
91
|
+
def initialize config
|
|
92
|
+
@config = config
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def index type = :html
|
|
96
|
+
case type
|
|
97
|
+
when :html
|
|
98
|
+
{:articles => articles.reverse.map do |article|
|
|
99
|
+
Article.new File.new(article), @config
|
|
100
|
+
end }.merge(:archives => archives)
|
|
101
|
+
when :xml, :json
|
|
102
|
+
return :articles => articles.map do |article|
|
|
103
|
+
Article.new File.new(article), @config
|
|
104
|
+
end
|
|
105
|
+
else return {}
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def article route
|
|
110
|
+
Article.new(File.new("#{Paths[:articles]}/#{route.join('-')}.#{self[:ext]}"), @config).load
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def /
|
|
114
|
+
self[:root]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def go route, type = :html
|
|
118
|
+
route << self./ if route.empty?
|
|
119
|
+
type, path = type =~ /html|xml|json/ ? type.to_sym : :html, route.join('/')
|
|
120
|
+
context = lambda do |data, page|
|
|
121
|
+
Context.new(data, @config, path).render(page, type)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
body, status = if Context.new.respond_to?(:"to_#{type}")
|
|
125
|
+
if route.first =~ /\d{4}/
|
|
126
|
+
case route.size
|
|
127
|
+
when 1..3
|
|
128
|
+
context[{:archives => archives(route * '-')}, :archives]
|
|
129
|
+
when 4
|
|
130
|
+
context[article(route), :article]
|
|
131
|
+
else http 400
|
|
132
|
+
end
|
|
133
|
+
elsif respond_to?(path)
|
|
134
|
+
context[send(path, type), path.to_sym]
|
|
135
|
+
elsif (repo = @config[:github][:repos].grep(/#{path}/).first) &&
|
|
136
|
+
!@config[:github][:user].empty?
|
|
137
|
+
context[Repo.new(repo, @config), :repo]
|
|
138
|
+
else
|
|
139
|
+
context[{}, path.to_sym]
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
http 400
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
rescue Errno::ENOENT => e
|
|
146
|
+
return :body => http(404).first, :type => :html, :status => 404
|
|
147
|
+
else
|
|
148
|
+
return :body => body || "", :type => type, :status => status || 200
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
protected
|
|
152
|
+
|
|
153
|
+
def http code
|
|
154
|
+
return ["<font style='font-size:300%'>toto, we're not in Kansas anymore (#{code})</font>", code]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class Context
|
|
158
|
+
include Template
|
|
159
|
+
include PageHelpers
|
|
160
|
+
|
|
161
|
+
def initialize ctx = {}, config = {}, path = "/"
|
|
162
|
+
@config, @context, @path = config, ctx, path
|
|
163
|
+
@articles = articles(@config[:ext]).reverse.map do |a|
|
|
164
|
+
Article.new(File.new(a), @config)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
ctx.each do |k, v|
|
|
168
|
+
meta_def(k) { ctx.instance_of?(Hash) ? v : ctx.send(k) }
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def render page, type
|
|
173
|
+
type == :html ? to_html(:layout, &Proc.new { to_html page }) : send(:"to_#{type}", :feed)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def to_xml page
|
|
177
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
|
178
|
+
instance_eval File.read("#{Paths[:templates]}/#{page}.builder")
|
|
179
|
+
end
|
|
180
|
+
alias :to_atom to_xml
|
|
181
|
+
|
|
182
|
+
def method_missing m, *args, &blk
|
|
183
|
+
@context.respond_to?(m) ? @context.send(m) : super
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class Repo < Hash
|
|
189
|
+
include Template
|
|
190
|
+
|
|
191
|
+
README = "http://github.com/%s/%s/raw/master/README.%s"
|
|
192
|
+
|
|
193
|
+
def initialize name, config
|
|
194
|
+
self[:name], @config = name, config
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def readme
|
|
198
|
+
markdown open(README %
|
|
199
|
+
[@config[:github][:user], self[:name], @config[:github][:ext]]).read
|
|
200
|
+
rescue Timeout::Error, OpenURI::HTTPError => e
|
|
201
|
+
"This page isn't available."
|
|
202
|
+
end
|
|
203
|
+
alias :content readme
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
class Archives < Array
|
|
207
|
+
include Template
|
|
208
|
+
|
|
209
|
+
def initialize articles
|
|
210
|
+
self.replace articles
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def to_html
|
|
214
|
+
super(:archives)
|
|
215
|
+
end
|
|
216
|
+
alias :to_s to_html
|
|
217
|
+
alias :archive archives
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class Article < Hash
|
|
221
|
+
include Template
|
|
222
|
+
|
|
223
|
+
def initialize obj, config = {}
|
|
224
|
+
@obj, @config = obj, config
|
|
225
|
+
self.load if obj.is_a? Hash
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def load
|
|
229
|
+
data = if @obj.is_a? File
|
|
230
|
+
meta, self[:body] = @obj.read.split(/\n\n/, 2)
|
|
231
|
+
@obj.close
|
|
232
|
+
YAML.load(meta)
|
|
233
|
+
elsif @obj.is_a? Hash
|
|
234
|
+
@obj
|
|
235
|
+
end.inject({}) {|h, (k,v)| h.merge(k.to_sym => v) }
|
|
236
|
+
|
|
237
|
+
self.taint
|
|
238
|
+
self.update data
|
|
239
|
+
self[:date] = Time.parse(self[:date].gsub('/', '-')) rescue Time.now
|
|
240
|
+
self
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def [] key
|
|
244
|
+
self.load unless self.tainted?
|
|
245
|
+
super
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def slug
|
|
249
|
+
self[:slug] || self[:title].slugize
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def summary length = nil
|
|
253
|
+
config = @config[:summary]
|
|
254
|
+
sum = if self[:body] =~ config[:delim]
|
|
255
|
+
self[:body].split(config[:delim]).first
|
|
256
|
+
else
|
|
257
|
+
self[:body].match(/(.{1,#{length || config[:length] || config[:max]}}.*?)(\n|\Z)/m).to_s
|
|
258
|
+
end
|
|
259
|
+
markdown(sum.length == self[:body].length ? sum : sum.strip.sub(/\.\Z/, '…'))
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def url
|
|
263
|
+
"http://#{(@config[:url].sub("http://", '') + self.path).squeeze('/')}"
|
|
264
|
+
end
|
|
265
|
+
alias :permalink url
|
|
266
|
+
|
|
267
|
+
def body
|
|
268
|
+
markdown self[:body].sub(@config[:summary][:delim], '') rescue markdown self[:body]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def title() self[:title] || "an article" end
|
|
272
|
+
def date() @config[:date, self[:date]] end
|
|
273
|
+
def path() self[:date].strftime("/%Y/%m/%d/#{slug}/") end
|
|
274
|
+
def author() self[:author] || @config[:author] end
|
|
275
|
+
def to_html() self.load; super(:article) end
|
|
276
|
+
|
|
277
|
+
alias :to_s to_html
|
|
278
|
+
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
class Config < Hash
|
|
282
|
+
Defaults = {
|
|
283
|
+
:author => ENV['USER'], # blog author
|
|
284
|
+
:title => Dir.pwd.split('/').last, # site title
|
|
285
|
+
:root => "index", # site index
|
|
286
|
+
:url => "http://127.0.0.1",
|
|
287
|
+
:date => lambda {|now| now.strftime("%d/%m/%Y") }, # date function
|
|
288
|
+
:markdown => :smart, # use markdown
|
|
289
|
+
:disqus => false, # disqus name
|
|
290
|
+
:summary => {:max => 150, :delim => /~\n/}, # length of summary and delimiter
|
|
291
|
+
:ext => 'txt', # extension for articles
|
|
292
|
+
:cache => 28800, # cache duration (seconds)
|
|
293
|
+
:github => {:user => "", :repos => [], :ext => 'md'}# Github username and list of repos
|
|
294
|
+
}
|
|
295
|
+
def initialize obj
|
|
296
|
+
self.update Defaults
|
|
297
|
+
self.update obj
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def set key, val
|
|
301
|
+
if val.is_a? Hash
|
|
302
|
+
self[key].update val
|
|
303
|
+
else
|
|
304
|
+
self[key] = val
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def [] key, *args
|
|
309
|
+
val = super(key)
|
|
310
|
+
val.respond_to?(:call) ? val.call(*args) : val
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
class Server
|
|
315
|
+
attr_reader :config
|
|
316
|
+
|
|
317
|
+
def initialize config = {}, &blk
|
|
318
|
+
@config = config.is_a?(Config) ? config : Config.new(config)
|
|
319
|
+
@config.instance_eval(&blk) if block_given?
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def call env
|
|
323
|
+
@request = Rack::Request.new env
|
|
324
|
+
@response = Rack::Response.new
|
|
325
|
+
|
|
326
|
+
return [400, {}, []] unless @request.get?
|
|
327
|
+
|
|
328
|
+
path, mime = @request.path_info.split('.')
|
|
329
|
+
route = (path || '/').split('/').reject {|i| i.empty? }
|
|
330
|
+
|
|
331
|
+
response = Toto::Site.new(@config).go(route, *(mime ? mime : []))
|
|
332
|
+
|
|
333
|
+
@response.body = [response[:body]]
|
|
334
|
+
@response['Content-Length'] = response[:body].length.to_s unless response[:body].empty?
|
|
335
|
+
@response['Content-Type'] = Rack::Mime.mime_type(".#{response[:type]}")
|
|
336
|
+
|
|
337
|
+
# Set http cache headers
|
|
338
|
+
@response['Cache-Control'] = if Toto.env == 'production'
|
|
339
|
+
"public, max-age=#{@config[:cache]}"
|
|
340
|
+
else
|
|
341
|
+
"no-cache, must-revalidate"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
@response['Etag'] = Digest::SHA1.hexdigest(response[:body])
|
|
345
|
+
|
|
346
|
+
@response.status = response[:status]
|
|
347
|
+
@response.finish
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ceilingfish-toto
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- cloudhead
|
|
@@ -10,7 +10,7 @@ autorequire:
|
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
12
|
|
|
13
|
-
date: 2010-02-
|
|
13
|
+
date: 2010-02-15 00:00:00 +00:00
|
|
14
14
|
default_executable:
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
@@ -70,6 +70,8 @@ files:
|
|
|
70
70
|
- Rakefile
|
|
71
71
|
- VERSION
|
|
72
72
|
- ceilingfish-toto.gemspec
|
|
73
|
+
- lib/ext/ext.rb
|
|
74
|
+
- lib/toto.rb
|
|
73
75
|
- test/articles/1900-05-17-the-wonderful-wizard-of-oz.txt
|
|
74
76
|
- test/articles/2001-01-01-two-thousand-and-one.txt
|
|
75
77
|
- test/articles/2009-04-01-tilt-factor.txt
|