toto_prerelease 0.4.7prerelease
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/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.md +162 -0
- data/Rakefile +35 -0
- data/TODO +2 -0
- data/VERSION +1 -0
- data/lib/ext/ext.rb +46 -0
- data/lib/toto.rb +358 -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.rhtml +1 -0
- data/test/templates/archives.rhtml +5 -0
- data/test/templates/article.rhtml +4 -0
- data/test/templates/feed.builder +21 -0
- data/test/templates/index.builder +21 -0
- data/test/templates/index.rhtml +13 -0
- data/test/templates/layout.rhtml +4 -0
- data/test/templates/repo.rhtml +1 -0
- data/test/test_helper.rb +44 -0
- data/test/toto_test.rb +286 -0
- data/toto.gemspec +80 -0
- metadata +151 -0
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") }
|
@@ -0,0 +1 @@
|
|
1
|
+
<span id="count"><%= @articles.length %></span>
|
@@ -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.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,13 @@
|
|
1
|
+
<ul id="articles">
|
2
|
+
<% for article in articles[0...3] %>
|
3
|
+
<li><%= article %></li>
|
4
|
+
<% end %>
|
5
|
+
</ul>
|
6
|
+
<div id="archives">
|
7
|
+
<%= archives[3...5] %>
|
8
|
+
</div>
|
9
|
+
<!-- testing env variable passing -->
|
10
|
+
<p>env passed: <%= env != nil %><br/></p>
|
11
|
+
<!-- testing get/post parameter passing -->
|
12
|
+
<p>request method type: <%= env['REQUEST_METHOD'] %><br/></p>
|
13
|
+
<p>request name value pair: <%= env['QUERY_STRING'] %><br/></p>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= readme %>
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'riot'
|
4
|
+
|
5
|
+
$:.unshift File.dirname(__FILE__)
|
6
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
+
|
8
|
+
require 'toto'
|
9
|
+
|
10
|
+
module Toto
|
11
|
+
class IncludesHTMLMacro < Riot::AssertionMacro
|
12
|
+
register :includes_html
|
13
|
+
|
14
|
+
def evaluate(actual, expected)
|
15
|
+
doc = Hpricot.parse(actual)
|
16
|
+
expected = expected.to_a.flatten
|
17
|
+
|
18
|
+
if (doc/expected.first).empty?
|
19
|
+
fail("expected #{actual} to contain a <#{expected.first}>")
|
20
|
+
elsif !(doc/expected.first).inner_html.match(expected.last)
|
21
|
+
fail("expected <#{expected.first}> to contain #{expected.last}")
|
22
|
+
else
|
23
|
+
pass
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class IncludesElementsMacro < Riot::AssertionMacro
|
29
|
+
register :includes_elements
|
30
|
+
|
31
|
+
def evaluate(actual, selector, count)
|
32
|
+
doc = Hpricot.parse(actual)
|
33
|
+
(doc/selector).size == count ? pass : fail("expected #{actual} to contain #{count} #{selector}(s)")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class WithinMacro < Riot::AssertionMacro
|
38
|
+
register :within
|
39
|
+
|
40
|
+
def evaluate(actual, expected)
|
41
|
+
expected.include?(actual) ? pass : fail("expected #{actual} to be within #{expected}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/test/toto_test.rb
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
URL = "http://toto.oz"
|
5
|
+
AUTHOR = "toto"
|
6
|
+
|
7
|
+
context Toto do
|
8
|
+
setup do
|
9
|
+
@config = Toto::Config.new(:markdown => true, :author => AUTHOR, :url => URL)
|
10
|
+
@toto = Rack::MockRequest.new(Toto::Server.new(@config))
|
11
|
+
Toto::Paths[:articles] = "test/articles"
|
12
|
+
Toto::Paths[:pages] = "test/templates"
|
13
|
+
Toto::Paths[:templates] = "test/templates"
|
14
|
+
end
|
15
|
+
|
16
|
+
context "GET /" do
|
17
|
+
setup { @toto.get('/') }
|
18
|
+
|
19
|
+
asserts("returns a 200") { topic.status }.equals 200
|
20
|
+
asserts("body is not empty") { not topic.body.empty? }
|
21
|
+
asserts("content type is set properly") { topic.content_type }.equals "text/html"
|
22
|
+
should("include a couple of article") { topic.body }.includes_elements("#articles li", 3)
|
23
|
+
should("include an archive") { topic.body }.includes_elements("#archives li", 2)
|
24
|
+
|
25
|
+
context "with no articles" do
|
26
|
+
setup { Rack::MockRequest.new(Toto::Server.new(@config.merge(:ext => 'oxo'))).get('/') }
|
27
|
+
|
28
|
+
asserts("body is not empty") { not topic.body.empty? }
|
29
|
+
asserts("returns a 200") { topic.status }.equals 200
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with a user-defined to_html" do
|
33
|
+
setup do
|
34
|
+
@config[:to_html] = lambda do |path, page, binding|
|
35
|
+
ERB.new(File.read("#{path}/#{page}.rhtml")).result(binding)
|
36
|
+
end
|
37
|
+
@toto.get('/')
|
38
|
+
end
|
39
|
+
|
40
|
+
asserts("returns a 200") { topic.status }.equals 200
|
41
|
+
asserts("body is not empty") { not topic.body.empty? }
|
42
|
+
asserts("content type is set properly") { topic.content_type }.equals "text/html"
|
43
|
+
should("include a couple of article") { topic.body }.includes_elements("#articles li", 3)
|
44
|
+
should("include an archive") { topic.body }.includes_elements("#archives li", 2)
|
45
|
+
asserts("Etag header present") { topic.headers.include? "ETag" }
|
46
|
+
asserts("Etag header has a value") { not topic.headers["ETag"].empty? }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "GET /about" do
|
51
|
+
setup { @toto.get('/about') }
|
52
|
+
asserts("returns a 200") { topic.status }.equals 200
|
53
|
+
asserts("body is not empty") { not topic.body.empty? }
|
54
|
+
should("have access to @articles") { topic.body }.includes_html("#count" => /5/)
|
55
|
+
end
|
56
|
+
|
57
|
+
context "GET a single article" do
|
58
|
+
setup { @toto.get("/1900/05/17/the-wonderful-wizard-of-oz") }
|
59
|
+
asserts("returns a 200") { topic.status }.equals 200
|
60
|
+
asserts("content type is set properly") { topic.content_type }.equals "text/html"
|
61
|
+
should("contain the article") { topic.body }.includes_html("p" => /<em>Once upon a time<\/em>/)
|
62
|
+
end
|
63
|
+
|
64
|
+
context "GET to the archive" do
|
65
|
+
context "through a year" do
|
66
|
+
setup { @toto.get('/2009') }
|
67
|
+
asserts("returns a 200") { topic.status }.equals 200
|
68
|
+
should("includes the entries for that year") { topic.body }.includes_elements("li.entry", 3)
|
69
|
+
end
|
70
|
+
|
71
|
+
context "through a year & month" do
|
72
|
+
setup { @toto.get('/2009/12') }
|
73
|
+
asserts("returns a 200") { topic.status }.equals 200
|
74
|
+
should("includes the entries for that month") { topic.body }.includes_elements("li.entry", 2)
|
75
|
+
should("includes the year & month") { topic.body }.includes_html("h1" => /2009\/12/)
|
76
|
+
end
|
77
|
+
|
78
|
+
context "through /archive" do
|
79
|
+
setup { @toto.get('/archive') }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "GET to an unknown route with a custom error" do
|
84
|
+
setup do
|
85
|
+
@config[:error] = lambda {|code| "error: #{code}" }
|
86
|
+
@toto.get('/unknown')
|
87
|
+
end
|
88
|
+
|
89
|
+
should("returns a 404") { topic.status }.equals 404
|
90
|
+
should("return the custom error") { topic.body }.equals "error: 404"
|
91
|
+
end
|
92
|
+
|
93
|
+
context "Request is invalid" do
|
94
|
+
setup { @toto.delete('/invalid') }
|
95
|
+
should("returns a 400") { topic.status }.equals 400
|
96
|
+
end
|
97
|
+
|
98
|
+
context "GET /index.xml (atom feed)" do
|
99
|
+
setup { @toto.get('/index.xml') }
|
100
|
+
asserts("content type is set properly") { topic.content_type }.equals "application/xml"
|
101
|
+
asserts("body should be valid xml") { topic.body }.includes_html("feed > entry" => /.+/)
|
102
|
+
asserts("summary shouldn't be empty") { topic.body }.includes_html("summary" => /.{10,}/)
|
103
|
+
end
|
104
|
+
|
105
|
+
context "GET /index?param=testparam (get parameter)" do
|
106
|
+
setup { @toto.get('/index?param=testparam') }
|
107
|
+
asserts("returns a 200") { topic.status }.equals 200
|
108
|
+
asserts("content type is set properly") { topic.content_type }.equals "text/html"
|
109
|
+
asserts("contain the env variable") { topic.body }.includes_html("p" => /env passed: true/)
|
110
|
+
asserts("access the http get parameter") { topic.body }.includes_html("p" => /request method type: GET/)
|
111
|
+
asserts("access the http parameter name value pair") { topic.body }.includes_html("p" => /request name value pair: param=testparam/)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
context "GET to a repo name" do
|
117
|
+
setup do
|
118
|
+
class Toto::Repo
|
119
|
+
def readme() "#{self[:name]}'s README" end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "when the repo is in the :repos array" do
|
124
|
+
setup do
|
125
|
+
@config[:github] = {:user => "cloudhead", :repos => ['the-repo']}
|
126
|
+
@toto.get('/the-repo')
|
127
|
+
end
|
128
|
+
should("return the-repo's README") { topic.body }.includes("the-repo's README")
|
129
|
+
end
|
130
|
+
|
131
|
+
context "when the repo is not in the :repos array" do
|
132
|
+
setup do
|
133
|
+
@config[:github] = {:user => "cloudhead", :repos => []}
|
134
|
+
@toto.get('/the-repo')
|
135
|
+
end
|
136
|
+
should("return a 404") { topic.status }.equals 404
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "creating an article" do
|
141
|
+
setup do
|
142
|
+
@config[:markdown] = true
|
143
|
+
@config[:date] = lambda {|t| "the time is #{t.strftime("%Y/%m/%d %H:%M")}" }
|
144
|
+
@config[:summary] = {:length => 50}
|
145
|
+
end
|
146
|
+
|
147
|
+
context "with the bare essentials" do
|
148
|
+
setup do
|
149
|
+
Toto::Article.new({
|
150
|
+
:title => "Toto & The Wizard of Oz.",
|
151
|
+
:body => "#Chapter I\nhello, *stranger*."
|
152
|
+
}, @config)
|
153
|
+
end
|
154
|
+
|
155
|
+
should("have a title") { topic.title }.equals "Toto & The Wizard of Oz."
|
156
|
+
should("parse the body as markdown") { topic.body }.equals "<h1>Chapter I</h1>\n\n<p>hello, <em>stranger</em>.</p>\n"
|
157
|
+
should("create an appropriate slug") { topic.slug }.equals "toto-and-the-wizard-of-oz"
|
158
|
+
should("set the date") { topic.date }.equals "the time is #{Date.today.strftime("%Y/%m/%d %H:%M")}"
|
159
|
+
should("create a summary") { topic.summary == topic.body }
|
160
|
+
should("have an author") { topic.author }.equals AUTHOR
|
161
|
+
should("have a path") { topic.path }.equals Date.today.strftime("/%Y/%m/%d/toto-and-the-wizard-of-oz/")
|
162
|
+
should("have a url") { topic.url }.equals Date.today.strftime("#{URL}/%Y/%m/%d/toto-and-the-wizard-of-oz/")
|
163
|
+
end
|
164
|
+
|
165
|
+
context "with a user-defined summary" do
|
166
|
+
setup do
|
167
|
+
Toto::Article.new({
|
168
|
+
:title => "Toto & The Wizard of Oz.",
|
169
|
+
:body => "Well,\nhello ~\n, *stranger*."
|
170
|
+
}, @config.merge(:markdown => false, :summary => {:max => 150, :delim => /~\n/}))
|
171
|
+
end
|
172
|
+
|
173
|
+
should("split the article at the delimiter") { topic.summary }.equals "Well,\nhello"
|
174
|
+
should("not have the delimiter in the body") { topic.body !~ /~/ }
|
175
|
+
end
|
176
|
+
|
177
|
+
context "with everything specified" do
|
178
|
+
setup do
|
179
|
+
Toto::Article.new({
|
180
|
+
:title => "The Wizard of Oz",
|
181
|
+
:body => ("a little bit of text." * 5) + "\n" + "filler" * 10,
|
182
|
+
:date => "19/10/1976",
|
183
|
+
:slug => "wizard-of-oz",
|
184
|
+
:author => "toetoe"
|
185
|
+
}, @config)
|
186
|
+
end
|
187
|
+
|
188
|
+
should("parse the date") { [topic[:date].month, topic[:date].year] }.equals [10, 1976]
|
189
|
+
should("use the slug") { topic.slug }.equals "wizard-of-oz"
|
190
|
+
should("use the author") { topic.author }.equals "toetoe"
|
191
|
+
|
192
|
+
context "and long first paragraph" do
|
193
|
+
should("create a valid summary") { topic.summary }.equals "<p>" + ("a little bit of text." * 5).chop + "…</p>\n"
|
194
|
+
end
|
195
|
+
|
196
|
+
context "and a short first paragraph" do
|
197
|
+
setup do
|
198
|
+
@config[:markdown] = false
|
199
|
+
Toto::Article.new({:body => "there ain't such thing as a free lunch\n" * 10}, @config)
|
200
|
+
end
|
201
|
+
|
202
|
+
should("create a valid summary") { topic.summary.size }.within 75..80
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "in a subdirectory" do
|
207
|
+
context "with implicit leading forward slash" do
|
208
|
+
setup do
|
209
|
+
conf = Toto::Config.new({})
|
210
|
+
conf.set(:prefix, "blog")
|
211
|
+
Toto::Article.new({
|
212
|
+
:title => "Toto & The Wizard of Oz.",
|
213
|
+
:body => "#Chapter I\nhello, *stranger*."
|
214
|
+
}, conf)
|
215
|
+
end
|
216
|
+
|
217
|
+
should("be in the directory") { topic.path }.equals Date.today.strftime("/blog/%Y/%m/%d/toto-and-the-wizard-of-oz/")
|
218
|
+
end
|
219
|
+
|
220
|
+
context "with explicit leading forward slash" do
|
221
|
+
setup do
|
222
|
+
conf = Toto::Config.new({})
|
223
|
+
conf.set(:prefix, "/blog")
|
224
|
+
Toto::Article.new({
|
225
|
+
:title => "Toto & The Wizard of Oz.",
|
226
|
+
:body => "#Chapter I\nhello, *stranger*."
|
227
|
+
}, conf)
|
228
|
+
end
|
229
|
+
|
230
|
+
should("be in the directory") { topic.path }.equals Date.today.strftime("/blog/%Y/%m/%d/toto-and-the-wizard-of-oz/")
|
231
|
+
end
|
232
|
+
|
233
|
+
context "with explicit trailing forward slash" do
|
234
|
+
setup do
|
235
|
+
conf = Toto::Config.new({})
|
236
|
+
conf.set(:prefix, "blog/")
|
237
|
+
Toto::Article.new({
|
238
|
+
:title => "Toto & The Wizard of Oz.",
|
239
|
+
:body => "#Chapter I\nhello, *stranger*."
|
240
|
+
}, conf)
|
241
|
+
end
|
242
|
+
|
243
|
+
should("be in the directory") { topic.path }.equals Date.today.strftime("/blog/%Y/%m/%d/toto-and-the-wizard-of-oz/")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "using Config#set with a hash" do
|
249
|
+
setup do
|
250
|
+
conf = Toto::Config.new({})
|
251
|
+
conf.set(:summary, {:delim => /%/})
|
252
|
+
conf
|
253
|
+
end
|
254
|
+
|
255
|
+
should("set summary[:delim] to /%/") { topic[:summary][:delim].source }.equals "%"
|
256
|
+
should("leave the :max intact") { topic[:summary][:max] }.equals 150
|
257
|
+
end
|
258
|
+
|
259
|
+
context "using Config#set with a block" do
|
260
|
+
setup do
|
261
|
+
conf = Toto::Config.new({})
|
262
|
+
conf.set(:to_html) {|path, p, _| path + p }
|
263
|
+
conf
|
264
|
+
end
|
265
|
+
|
266
|
+
should("set the value to a proc") { topic[:to_html] }.respond_to :call
|
267
|
+
end
|
268
|
+
|
269
|
+
context "testing individual configuration parameters" do
|
270
|
+
context "generate error pages" do
|
271
|
+
setup do
|
272
|
+
conf = Toto::Config.new({})
|
273
|
+
conf.set(:error) {|code| "error code #{code}" }
|
274
|
+
conf
|
275
|
+
end
|
276
|
+
|
277
|
+
should("create an error page") { topic[:error].call(400) }.equals "error code 400"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
context "extensions to the core Ruby library" do
|
282
|
+
should("respond to iso8601") { Date.today }.respond_to?(:iso8601)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
|