nesta 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,32 @@
1
+ = 0.9.5 / (1 May 2011)
2
+
3
+ * Added --version option to nesta command (Christopher Lindblom).
4
+
5
+ * Upgraded Haml and Sass to version 3.1 (Andrew Nesbitt).
6
+
7
+ * Updated the URL from which the Disqus JavaScript is loaded
8
+ (Sidharta Surya Kusnanto).
9
+
10
+ * Bug fix: Don't use parent's heading in page title if parent's heading
11
+ is blank (Christopher Lindblom).
12
+
13
+ * Bug fix: Removed trailing whitespace inside <a> tags generated by
14
+ the display_menu() helper.
15
+
16
+ * Updated the URL from which Disqus JavaScript is loaded
17
+ (Sidharta Surya Kusnanto).
18
+
19
+ * Bug fix: Made article_summaries render summaries for the pages
20
+ passed into it (Robert Syme).
21
+
22
+ * Bug fix: Empty files in the content directory would cause the site to
23
+ crash - they are now served properly.
24
+
25
+ * Bug fix: When pages written in Haml were included on a category page
26
+ the default theme didn't provide access to Sinatra's helper methods
27
+ within Haml templates. See https://github.com/gma/nesta/pull/18
28
+ (Carl Furrow).
29
+
1
30
  = 0.9.4 / 18 February 2011
2
31
 
3
32
  * Updated the link colours in the default theme.
data/Gemfile.lock CHANGED
@@ -1,18 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nesta (0.9.4)
4
+ nesta (0.9.5)
5
5
  RedCloth (~> 4.2)
6
- haml (~> 3.0)
6
+ haml (~> 3.1)
7
7
  maruku (>= 0.6.0)
8
+ sass (~> 3.1)
8
9
  shotgun (>= 0.8)
9
10
  sinatra (= 1.1.2)
10
11
 
11
12
  GEM
12
13
  remote: http://rubygems.org/
13
14
  specs:
14
- RedCloth (4.2.3)
15
- haml (3.0.25)
15
+ RedCloth (4.2.7)
16
+ haml (3.1.1)
16
17
  hoe (2.6.2)
17
18
  rake (>= 0.8.7)
18
19
  rubyforge (>= 2.0.4)
@@ -28,7 +29,8 @@ GEM
28
29
  rspec_hpricot_matchers (1.0)
29
30
  rubyforge (2.0.4)
30
31
  json_pure (>= 1.1.7)
31
- shotgun (0.8)
32
+ sass (3.1.1)
33
+ shotgun (0.9)
32
34
  rack (>= 1.0)
33
35
  sinatra (1.1.2)
34
36
  rack (~> 1.1)
@@ -36,20 +38,15 @@ GEM
36
38
  syntax (1.0.0)
37
39
  test-unit (1.2.3)
38
40
  hoe (>= 1.5.1)
39
- tilt (1.2.1)
41
+ tilt (1.3)
40
42
 
41
43
  PLATFORMS
42
44
  ruby
43
45
 
44
46
  DEPENDENCIES
45
- RedCloth (~> 4.2)
46
- haml (~> 3.0)
47
47
  hpricot (= 0.8.3)
48
- maruku (>= 0.6.0)
49
48
  nesta!
50
49
  rack-test (= 0.5.7)
51
50
  rspec (= 1.3.0)
52
51
  rspec_hpricot_matchers (= 1.0)
53
- shotgun (>= 0.8)
54
- sinatra (= 1.1.2)
55
52
  test-unit (= 1.2.3)
data/README.md CHANGED
@@ -24,7 +24,7 @@ Then use the `nesta` command to generate a new site:
24
24
  Install a few dependencies, and you're away:
25
25
 
26
26
  $ cd mysite.com
27
- $ bundle install
27
+ $ bundle
28
28
 
29
29
  You'll find basic configuration options for your site in
30
30
  `config/config.yml`. The defaults will work, but you'll want to tweak it
@@ -36,7 +36,7 @@ shotgun...
36
36
  $ bundle exec shotgun config.ru
37
37
 
38
38
  ...then point your web browser at http://localhost:9393. Start editing
39
- the files in `content/pages` (see [Creating Your Content][content] for
40
- full instructions).
39
+ the files in `content/pages` (see the [Writing content][content] docs
40
+ for full instructions).
41
41
 
42
- [content]: http://effectif.com/nesta/creating-content
42
+ [content]: http://nestacms.com/docs/creating-content
data/bin/nesta CHANGED
@@ -12,6 +12,7 @@ USAGE: #{File.basename($0)} [GLOBAL OPTIONS] <command> [COMMAND OPTIONS]
12
12
 
13
13
  GLOBAL OPTIONS
14
14
  --help, -h Display this message.
15
+ --version, -v Display version number.
15
16
 
16
17
  COMMANDS
17
18
  new <path> Create a new Nesta project.
@@ -28,10 +29,16 @@ EOF
28
29
  exit 0
29
30
  end
30
31
 
32
+ def self.version
33
+ puts "Nesta #{Nesta::VERSION}"
34
+ exit 0
35
+ end
36
+
31
37
  def self.parse_command_line
32
38
  opts = GetoptLong.new(
33
39
  ['--git', GetoptLong::NO_ARGUMENT],
34
40
  ['--help', '-h', GetoptLong::NO_ARGUMENT],
41
+ ['--version', '-v', GetoptLong::NO_ARGUMENT],
35
42
  ['--vlad', GetoptLong::NO_ARGUMENT]
36
43
  )
37
44
  options = {}
@@ -39,6 +46,8 @@ exit 0
39
46
  case opt
40
47
  when '--help'
41
48
  usage
49
+ when '--version'
50
+ version
42
51
  else
43
52
  options[opt.sub(/^--/, '')] = arg
44
53
  end
data/lib/nesta/app.rb CHANGED
@@ -98,11 +98,7 @@ module Nesta
98
98
  end
99
99
 
100
100
  def article_summaries(articles)
101
- haml(
102
- :summaries,
103
- :layout => false,
104
- :locals => { :pages => latest_articles }
105
- )
101
+ haml(:summaries, :layout => false, :locals => { :pages => articles })
106
102
  end
107
103
  end
108
104
 
data/lib/nesta/models.rb CHANGED
@@ -52,7 +52,12 @@ module Nesta
52
52
  def initialize(filename)
53
53
  @filename = filename
54
54
  @format = filename.split(".").last.to_sym
55
- parse_file
55
+ if File.zero?(filename)
56
+ @metadata = {}
57
+ @markup = ''
58
+ else
59
+ @metadata, @markup = parse_file
60
+ end
56
61
  @mtime = File.mtime(filename)
57
62
  end
58
63
 
@@ -86,14 +91,7 @@ module Nesta
86
91
  end
87
92
 
88
93
  def to_html(scope = nil)
89
- case @format
90
- when :mdown
91
- Maruku.new(markup).to_html
92
- when :haml
93
- Haml::Engine.new(markup).to_html(scope || Object.new)
94
- when :textile
95
- RedCloth.new(markup).to_html
96
- end
94
+ convert_to_html(@format, scope, markup)
97
95
  end
98
96
 
99
97
  def last_modified
@@ -113,29 +111,41 @@ module Nesta
113
111
  end
114
112
 
115
113
  private
116
- def markup
117
- @markup
118
- end
114
+ def markup
115
+ @markup
116
+ end
119
117
 
120
- def paragraph_is_metadata(text)
121
- text.split("\n").first =~ /^[\w ]+:/
122
- end
118
+ def metadata?(text)
119
+ text.split("\n").first =~ /^[\w ]+:/
120
+ end
123
121
 
124
- def parse_file
125
- first_para, remaining = File.open(@filename).read.split(/\r?\n\r?\n/, 2)
126
- @metadata = {}
127
- if paragraph_is_metadata(first_para)
128
- @markup = remaining
129
- for line in first_para.split("\n") do
130
- key, value = line.split(/\s*:\s*/, 2)
131
- @metadata[key.downcase] = value.chomp
132
- end
122
+ def parse_file
123
+ contents = File.open(@filename).read
124
+ rescue Errno::ENOENT
125
+ raise Sinatra::NotFound
133
126
  else
134
- @markup = [first_para, remaining].join("\n\n")
127
+ first_paragraph, remaining = contents.split(/\r?\n\r?\n/, 2)
128
+ metadata = {}
129
+ if metadata?(first_paragraph)
130
+ first_paragraph.split("\n").each do |line|
131
+ key, value = line.split(/\s*:\s*/, 2)
132
+ metadata[key.downcase] = value.chomp
133
+ end
134
+ end
135
+ markup = metadata?(first_paragraph) ? remaining : contents
136
+ return metadata, markup
137
+ end
138
+
139
+ def convert_to_html(format, scope, text)
140
+ case format
141
+ when :mdown
142
+ Maruku.new(text).to_html
143
+ when :haml
144
+ Haml::Engine.new(text).to_html(scope)
145
+ when :textile
146
+ RedCloth.new(text).to_html
147
+ end
135
148
  end
136
- rescue Errno::ENOENT # file not found
137
- raise Sinatra::NotFound
138
- end
139
149
  end
140
150
 
141
151
  class Page < FileModel
@@ -173,7 +183,7 @@ module Nesta
173
183
  def title
174
184
  if metadata('title')
175
185
  metadata('title')
176
- elsif parent
186
+ elsif parent && (! parent.heading.nil?)
177
187
  "#{heading} - #{parent.heading}"
178
188
  elsif heading
179
189
  "#{heading} - #{Nesta::Config.title}"
@@ -212,18 +222,16 @@ module Nesta
212
222
  end
213
223
  end
214
224
 
215
- def body
216
- case @format
217
- when :mdown
218
- body_text = markup.sub(/^#[^#].*$\r?\n(\r?\n)?/, "")
219
- Maruku.new(body_text).to_html
220
- when :haml
221
- body_text = markup.sub(/^\s*%h1\s+.*$\r?\n(\r?\n)?/, "")
222
- Haml::Engine.new(body_text).render
223
- when :textile
224
- body_text = markup.sub(/^\s*h1\.\s+.*$\r?\n(\r?\n)?/, "")
225
- RedCloth.new(body_text).to_html
226
- end
225
+ def body(scope = nil)
226
+ body_text = case @format
227
+ when :mdown
228
+ markup.sub(/^#[^#].*$\r?\n(\r?\n)?/, "")
229
+ when :haml
230
+ markup.sub(/^\s*%h1\s+.*$\r?\n(\r?\n)?/, "")
231
+ when :textile
232
+ markup.sub(/^\s*h1\.\s+.*$\r?\n(\r?\n)?/, "")
233
+ end
234
+ convert_to_html(@format, scope, body_text)
227
235
  end
228
236
 
229
237
  def categories
@@ -256,9 +264,10 @@ module Nesta
256
264
  end
257
265
 
258
266
  def pages
259
- Page.find_all.select do |page|
267
+ in_category = Page.find_all.select do |page|
260
268
  page.date.nil? && page.categories.include?(self)
261
- end.sort do |x, y|
269
+ end
270
+ in_category.sort do |x, y|
262
271
  by_priority = y.priority(path) <=> x.priority(path)
263
272
  if by_priority == 0
264
273
  x.heading.downcase <=> y.heading.downcase
@@ -22,7 +22,7 @@ module Nesta
22
22
  end
23
23
  else
24
24
  haml_tag :li do
25
- haml_tag :a, :href => item.abspath do
25
+ haml_tag :a, :<, :href => item.abspath do
26
26
  haml_concat item.heading
27
27
  end
28
28
  end
data/lib/nesta/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Nesta
2
- VERSION = '0.9.4'
2
+ VERSION = '0.9.5'
3
3
  end
data/nesta.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Graham Ashton"]
10
10
  s.email = ["graham@effectif.com"]
11
- s.homepage = "http://effectif.com/nesta"
11
+ s.homepage = "http://nestacms.com"
12
12
  s.summary = %q{Ruby CMS, written in Sinatra}
13
13
  s.description = <<-EOF
14
14
  Nesta is a lightweight Content Management System, written in Ruby using
@@ -31,7 +31,8 @@ EOF
31
31
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
32
32
  s.require_paths = ["lib"]
33
33
 
34
- s.add_dependency('haml', '~> 3.0')
34
+ s.add_dependency('haml', '~> 3.1')
35
+ s.add_dependency('sass', '~> 3.1')
35
36
  s.add_dependency('maruku', '>= 0.6.0')
36
37
  s.add_dependency('RedCloth', '~> 4.2')
37
38
  s.add_dependency('sinatra', '1.1.2')
data/spec/models_spec.rb CHANGED
@@ -285,6 +285,16 @@ describe "Page", :shared => true do
285
285
  end
286
286
  end
287
287
 
288
+ describe "with no content" do
289
+ it "should produce no HTML output" do
290
+ create_article do |path|
291
+ file = File.open(path, 'w')
292
+ file.close
293
+ end
294
+ Nesta::Page.find_all.first.to_html.should == ''
295
+ end
296
+ end
297
+
288
298
  describe "without metadata" do
289
299
  before(:each) do
290
300
  create_article
data/spec/page_spec.rb CHANGED
@@ -392,7 +392,7 @@ describe "A page" do
392
392
  it "should display Disqus comments" do
393
393
  do_get
394
394
  body.should have_tag('#disqus_thread')
395
- body.should have_tag('script[@src*="mysite/embed.js"]')
395
+ body.should have_tag('script[@src*="mysite.disqus.com/embed.js"]')
396
396
  end
397
397
  end
398
398
  end
@@ -420,6 +420,23 @@ describe "A Haml page" do
420
420
  get "/a-page"
421
421
  body.should have_tag("div", "23 November 2010")
422
422
  end
423
+
424
+ it "should access helpers when rendering articles on a category page" do
425
+ category = create_page(
426
+ :path => "a-page",
427
+ :heading => "First heading",
428
+ :content => "Blah blah"
429
+ )
430
+ create_article(
431
+ :path => "an-article",
432
+ :ext => :haml,
433
+ :heading => "First heading",
434
+ :metadata => { :categories => category.path },
435
+ :content => "%h1 Second heading\n\n%div= format_date(Date.new(2010, 11, 23))"
436
+ )
437
+ get "/a-page"
438
+ body.should have_tag("div", "23 November 2010")
439
+ end
423
440
  end
424
441
 
425
442
  describe "attachments" do
data/views/comments.haml CHANGED
@@ -1,8 +1,9 @@
1
1
  - if short_name = Nesta::Config.disqus_short_name
2
2
  #disqus_thread
3
3
  - if Sinatra::Application.environment == :development
4
- %script(type="text/javascript")
4
+ :javascript
5
5
  var disqus_developer = true;
6
- %script(type="text/javascript" src="http://disqus.com/forums/#{short_name}/embed.js")
6
+ %script{:type => 'text/javascript', :src => "http://#{short_name}.disqus.com/embed.js", :async => true}
7
+
7
8
  %noscript
8
- %a(href="http://disqus.com/forums/#{short_name}/?url=ref") View comments.
9
+ %a(href="http://#{short_name}.disqus.com/embed.js?url=ref") View comments.
data/views/footer.haml CHANGED
@@ -2,7 +2,4 @@
2
2
  %p
3
3
  Powered by Nesta, a
4
4
  = succeed "." do
5
- %a(href="http://effectif.com/nesta") Ruby CMS
6
- Design by
7
- = succeed "." do
8
- %a(href="http://effectif.com") Graham Ashton
5
+ %a(href="http://nestacms.com") Ruby CMS
data/views/page.haml CHANGED
@@ -6,13 +6,13 @@
6
6
  ~ @page.to_html(self)
7
7
 
8
8
  %section.pages
9
- = haml :summaries, :layout => false, :locals => { :pages => @page.pages }
9
+ = article_summaries(@page.pages)
10
10
 
11
11
  - unless @page.articles.empty?
12
12
  %section.articles
13
13
  %header
14
14
  %h1= "Articles on #{@page.heading}"
15
- = haml :summaries, :layout => false, :locals => { :pages => @page.articles, :heading => :h3 }
15
+ = article_summaries(@page.articles)
16
16
 
17
17
  = haml :page_meta, :layout => false, :locals => { :page => @page }
18
18
 
data/views/summaries.haml CHANGED
@@ -7,7 +7,7 @@
7
7
  %h1
8
8
  %a(href="#{page.abspath}")= page.heading
9
9
  - if page.summary.nil? || page.summary.empty?
10
- ~ page.body
10
+ ~ page.body(self)
11
11
  - else
12
12
  ~ page.summary
13
13
  %p.read_more
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nesta
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 9
8
- - 4
9
- version: 0.9.4
4
+ prerelease:
5
+ version: 0.9.5
10
6
  platform: ruby
11
7
  authors:
12
8
  - Graham Ashton
@@ -14,7 +10,7 @@ autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2011-02-18 00:00:00 +00:00
13
+ date: 2011-05-01 00:00:00 +01:00
18
14
  default_executable:
19
15
  dependencies:
20
16
  - !ruby/object:Gem::Dependency
@@ -24,145 +20,120 @@ dependencies:
24
20
  requirements:
25
21
  - - ~>
26
22
  - !ruby/object:Gem::Version
27
- segments:
28
- - 3
29
- - 0
30
- version: "3.0"
23
+ version: "3.1"
31
24
  type: :runtime
32
25
  prerelease: false
33
26
  version_requirements: *id001
34
27
  - !ruby/object:Gem::Dependency
35
- name: maruku
28
+ name: sass
36
29
  requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: "3.1"
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: maruku
40
+ requirement: &id003 !ruby/object:Gem::Requirement
37
41
  none: false
38
42
  requirements:
39
43
  - - ">="
40
44
  - !ruby/object:Gem::Version
41
- segments:
42
- - 0
43
- - 6
44
- - 0
45
45
  version: 0.6.0
46
46
  type: :runtime
47
47
  prerelease: false
48
- version_requirements: *id002
48
+ version_requirements: *id003
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: RedCloth
51
- requirement: &id003 !ruby/object:Gem::Requirement
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ~>
55
55
  - !ruby/object:Gem::Version
56
- segments:
57
- - 4
58
- - 2
59
56
  version: "4.2"
60
57
  type: :runtime
61
58
  prerelease: false
62
- version_requirements: *id003
59
+ version_requirements: *id004
63
60
  - !ruby/object:Gem::Dependency
64
61
  name: sinatra
65
- requirement: &id004 !ruby/object:Gem::Requirement
62
+ requirement: &id005 !ruby/object:Gem::Requirement
66
63
  none: false
67
64
  requirements:
68
65
  - - "="
69
66
  - !ruby/object:Gem::Version
70
- segments:
71
- - 1
72
- - 1
73
- - 2
74
67
  version: 1.1.2
75
68
  type: :runtime
76
69
  prerelease: false
77
- version_requirements: *id004
70
+ version_requirements: *id005
78
71
  - !ruby/object:Gem::Dependency
79
72
  name: shotgun
80
- requirement: &id005 !ruby/object:Gem::Requirement
73
+ requirement: &id006 !ruby/object:Gem::Requirement
81
74
  none: false
82
75
  requirements:
83
76
  - - ">="
84
77
  - !ruby/object:Gem::Version
85
- segments:
86
- - 0
87
- - 8
88
78
  version: "0.8"
89
79
  type: :runtime
90
80
  prerelease: false
91
- version_requirements: *id005
81
+ version_requirements: *id006
92
82
  - !ruby/object:Gem::Dependency
93
83
  name: hpricot
94
- requirement: &id006 !ruby/object:Gem::Requirement
84
+ requirement: &id007 !ruby/object:Gem::Requirement
95
85
  none: false
96
86
  requirements:
97
87
  - - "="
98
88
  - !ruby/object:Gem::Version
99
- segments:
100
- - 0
101
- - 8
102
- - 3
103
89
  version: 0.8.3
104
90
  type: :development
105
91
  prerelease: false
106
- version_requirements: *id006
92
+ version_requirements: *id007
107
93
  - !ruby/object:Gem::Dependency
108
94
  name: rack-test
109
- requirement: &id007 !ruby/object:Gem::Requirement
95
+ requirement: &id008 !ruby/object:Gem::Requirement
110
96
  none: false
111
97
  requirements:
112
98
  - - "="
113
99
  - !ruby/object:Gem::Version
114
- segments:
115
- - 0
116
- - 5
117
- - 7
118
100
  version: 0.5.7
119
101
  type: :development
120
102
  prerelease: false
121
- version_requirements: *id007
103
+ version_requirements: *id008
122
104
  - !ruby/object:Gem::Dependency
123
105
  name: rspec
124
- requirement: &id008 !ruby/object:Gem::Requirement
106
+ requirement: &id009 !ruby/object:Gem::Requirement
125
107
  none: false
126
108
  requirements:
127
109
  - - "="
128
110
  - !ruby/object:Gem::Version
129
- segments:
130
- - 1
131
- - 3
132
- - 0
133
111
  version: 1.3.0
134
112
  type: :development
135
113
  prerelease: false
136
- version_requirements: *id008
114
+ version_requirements: *id009
137
115
  - !ruby/object:Gem::Dependency
138
116
  name: rspec_hpricot_matchers
139
- requirement: &id009 !ruby/object:Gem::Requirement
117
+ requirement: &id010 !ruby/object:Gem::Requirement
140
118
  none: false
141
119
  requirements:
142
120
  - - "="
143
121
  - !ruby/object:Gem::Version
144
- segments:
145
- - 1
146
- - 0
147
122
  version: "1.0"
148
123
  type: :development
149
124
  prerelease: false
150
- version_requirements: *id009
125
+ version_requirements: *id010
151
126
  - !ruby/object:Gem::Dependency
152
127
  name: test-unit
153
- requirement: &id010 !ruby/object:Gem::Requirement
128
+ requirement: &id011 !ruby/object:Gem::Requirement
154
129
  none: false
155
130
  requirements:
156
131
  - - "="
157
132
  - !ruby/object:Gem::Version
158
- segments:
159
- - 1
160
- - 2
161
- - 3
162
133
  version: 1.2.3
163
134
  type: :development
164
135
  prerelease: false
165
- version_requirements: *id010
136
+ version_requirements: *id011
166
137
  description: |
167
138
  Nesta is a lightweight Content Management System, written in Ruby using
168
139
  the Sinatra web framework. Nesta has the simplicity of a static site
@@ -246,7 +217,7 @@ files:
246
217
  - views/sitemap.haml
247
218
  - views/summaries.haml
248
219
  has_rdoc: true
249
- homepage: http://effectif.com/nesta
220
+ homepage: http://nestacms.com
250
221
  licenses: []
251
222
 
252
223
  post_install_message:
@@ -259,7 +230,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
259
230
  requirements:
260
231
  - - ">="
261
232
  - !ruby/object:Gem::Version
262
- hash: 3842425981722162999
233
+ hash: -1498426163218200978
263
234
  segments:
264
235
  - 0
265
236
  version: "0"
@@ -268,14 +239,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
268
239
  requirements:
269
240
  - - ">="
270
241
  - !ruby/object:Gem::Version
271
- hash: 3842425981722162999
242
+ hash: -1498426163218200978
272
243
  segments:
273
244
  - 0
274
245
  version: "0"
275
246
  requirements: []
276
247
 
277
248
  rubyforge_project: nesta
278
- rubygems_version: 1.3.7
249
+ rubygems_version: 1.5.0
279
250
  signing_key:
280
251
  specification_version: 3
281
252
  summary: Ruby CMS, written in Sinatra