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 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.6
1
+ 0.3.7
@@ -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.6"
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-13}
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/, '&hellip;'))
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.6
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 00:00:00 +00:00
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