serif 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,26 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- serif (0.1.1)
4
+ serif (0.1.2)
5
5
  liquid (~> 2.4)
6
6
  pygments.rb (~> 0.3)
7
7
  rack (~> 1.0)
8
+ rack-rewrite (~> 1.3.0)
8
9
  rake (~> 0.9)
9
10
  redcarpet (~> 2.2)
10
11
  redhead (~> 0.0.6)
11
12
  sinatra (~> 1.3)
12
13
  slop (~> 3.3)
13
- yui-compressor
14
14
 
15
15
  GEM
16
16
  remote: http://rubygems.org/
17
17
  specs:
18
- POpen4 (0.1.4)
19
- Platform (>= 0.4.0)
20
- open4
21
- Platform (0.4.0)
22
18
  liquid (2.4.1)
23
- open4 (1.3.0)
24
19
  posix-spawn (0.3.6)
25
20
  pygments.rb (0.3.2)
26
21
  posix-spawn (~> 0.3.6)
@@ -28,6 +23,7 @@ GEM
28
23
  rack (1.4.1)
29
24
  rack-protection (1.2.0)
30
25
  rack
26
+ rack-rewrite (1.3.1)
31
27
  rake (0.9.2.2)
32
28
  redcarpet (2.2.2)
33
29
  redhead (0.0.6)
@@ -38,8 +34,6 @@ GEM
38
34
  slop (3.3.3)
39
35
  tilt (1.3.3)
40
36
  yajl-ruby (1.1.0)
41
- yui-compressor (0.9.6)
42
- POpen4 (>= 0.1.4)
43
37
 
44
38
  PLATFORMS
45
39
  ruby
data/README.md CHANGED
@@ -95,7 +95,8 @@ The structure of a Serif site is something like this:
95
95
  │   ├── 2012-02-28-another-post
96
96
  │   └── 2012-03-30-and-a-third
97
97
  ├── _templates
98
- │   └─── post.html
98
+ │   ├─── post.html
99
+ │   └─── archive_page.html
99
100
  ├── _trash
100
101
  ├── _config.yml
101
102
  ├── css
@@ -147,7 +148,12 @@ The headers are similar to Jekyll's YAML front matter, but here there are no for
147
148
 
148
149
  ## `_templates`
149
150
 
150
- This directory currently only has a single file in it, `post.html`, which is used to produce HTML content from Markdown files in `_posts`.
151
+ Two templates are available:
152
+
153
+ * `post.html`, which will be used to render individual post pages.
154
+ * `archive_page.html`, which will be used to render individual archive pages.
155
+
156
+ Both must be valid Liquid templates.
151
157
 
152
158
  ## `_trash`
153
159
 
@@ -207,6 +213,59 @@ In both cases, the output is, of course:
207
213
 
208
214
  If you have a file like `feed.xml` that you wish to _not_ be contained within a layout, specify `layout: none` in the header for the file.
209
215
 
216
+ # Archive pages
217
+
218
+ By default, archive pages are made available at `/archive/:year/month`, e.g., `/archive/2012/11`. Individual archive pages can be customised by editing the `_templates/archive_page.html` file, which is used for each month.
219
+
220
+ Within the `archive_page.html` template, you have access to the variables `month`, which is a Ruby Date instance, and `posts` for the posts within that month.
221
+
222
+ To disable archive pages, or configure the URL format, see the section on configuration.
223
+
224
+ ## Linking to archive pages
225
+
226
+ To link to archive pages, there is a `site.archives` template variable available in all pages. The structure of `site.archives` is a nested map starting at years:
227
+
228
+ ```
229
+ {
230
+ "posts" => [...],
231
+ "years" => [
232
+ {
233
+ "date" => Date.new(2012),
234
+ "posts" => [...],
235
+ "months" => [
236
+ { "date" => Date.new(2012, 12), "archive_url" => "/archive/2012/12", "posts" => [...] },
237
+ { "date" => Date.new(2012, 11), "archive_url" => "/archive/2012/11", "posts" => [...] },
238
+ ...
239
+ ]
240
+ }
241
+ ]
242
+ }
243
+ ```
244
+
245
+ Using `site.archives`, you can iterate over `years`, then iterate over `months` and use `archive_url` to link to the archive page for that given month within the year.
246
+
247
+ # Configuration
248
+
249
+ Configuration goes in `_config.yml` and must be valid YAML. Here's a sample configuration with available options:
250
+
251
+ ```
252
+ admin:
253
+ username: myusername
254
+ password: mypassword
255
+ permalink: /posts/:title
256
+ archive:
257
+ enabled: yes
258
+ url_format: /archive/:year/:month
259
+ ```
260
+
261
+ `admin` contains the credentials used when accessing the admin interface. This information is private, of course.
262
+
263
+ `permalink` is the URL format for individual post pages. The default permalink value is `/:title`. For an explanation of the format of permalinks, see above.
264
+
265
+ `archive` contains configuration options concerning archive pages. `enabled` can be used to toggle whether archive pages are generated. If set to `no` or `false`, no archive pages will be generated. By default, this value is `yes`.
266
+
267
+ The `archive` `url_format` configuration option is the format used for archive pages. The default value is `/archive/:year/:month`. **This must include both year and month.** Visiting, e.g., `/archive/2012/11` would render posts made in November 2012. See the section on archive pages above for more details.
268
+
210
269
  # Deploying
211
270
 
212
271
  To serve the site, set any web server to use `/path/to/site/directory/_site` as its root. *NOTE:* URLs generated in the site do not contain `.html` "extensions" by default, so you will need a rewrite rule. Here's an example rewrite for nginx:
data/lib/serif/config.rb CHANGED
@@ -21,5 +21,19 @@ class Config
21
21
  def permalink
22
22
  yaml["permalink"] || "/:title"
23
23
  end
24
+
25
+ def archive_enabled?
26
+ a = yaml["archive"]
27
+
28
+ if a
29
+ a["enabled"]
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def archive_url_format
36
+ (yaml["archive"] || {})["url_format"] || "/archive/:year/:month"
37
+ end
24
38
  end
25
39
  end
data/lib/serif/server.rb CHANGED
@@ -13,7 +13,16 @@ class DevelopmentServer
13
13
 
14
14
  # it seems Rack::Rewrite doesn't like public_folder files, so here we are
15
15
  get "*" do
16
- File.read(Dir[File.expand_path("_site#{params[:splat].join("/")}.*")].first)
16
+ # attempt the exact name + an extension
17
+ file = Dir[File.expand_path("_site#{params[:splat].join("/")}.*")].first
18
+
19
+ # try index.html under the directory if it failed. useful for archive directory requests.
20
+ file ||= Dir[File.expand_path("_site#{params[:splat].join("/")}/index.html")].first
21
+
22
+ # make a naive assumption that there's a 404 file at 404.html
23
+ file ||= Dir[File.expand_path("_site/404.html")].first
24
+
25
+ File.read(file)
17
26
  end
18
27
  end
19
28
 
data/lib/serif/site.rb CHANGED
@@ -19,6 +19,7 @@ else
19
19
  end
20
20
  end
21
21
 
22
+ module Serif
22
23
  module Filters
23
24
  def strip(input)
24
25
  input.strip
@@ -36,8 +37,9 @@ module Filters
36
37
  input.xmlschema
37
38
  end
38
39
  end
40
+ end
39
41
 
40
- Liquid::Template.register_filter(Filters)
42
+ Liquid::Template.register_filter(Serif::Filters)
41
43
 
42
44
  module Serif
43
45
  class Site
@@ -49,8 +51,10 @@ class Site
49
51
  @source_directory
50
52
  end
51
53
 
54
+ # Returns all of the site's posts, in reverse chronological order
55
+ # by creation time.
52
56
  def posts
53
- Post.all(self)
57
+ Post.all(self).sort_by { |entry| entry.created }.reverse
54
58
  end
55
59
 
56
60
  def drafts
@@ -78,7 +82,97 @@ class Site
78
82
  !%w[.html .xml].include?(File.extname(filename))
79
83
  end
80
84
 
81
- # TODO: fix all these File.join calls
85
+ # Returns the relative archive URL for the given date,
86
+ # using the value of config.archive_url_format
87
+ def archive_url_for_date(date)
88
+ format = config.archive_url_format
89
+
90
+ parts = {
91
+ "year" => date.year.to_s,
92
+ "month" => date.month.to_s.rjust(2, "0")
93
+ }
94
+
95
+ output = format
96
+
97
+ parts.each do |placeholder, value|
98
+ output = output.gsub(Regexp.quote(":" + placeholder), value)
99
+ end
100
+
101
+ output
102
+ end
103
+
104
+ # Returns a nested hash with the following structure:
105
+ #
106
+ # {
107
+ # :posts => [],
108
+ # :years => [
109
+ # {
110
+ # :date => Date.new(2012),
111
+ # :posts => [],
112
+ # :months => [
113
+ # { :date => Date.new(2012, 12), :archive_url => "/archive/2012/12", :posts => [] },
114
+ # { :date => Date.new(2012, 11), :archive_url => "/archive/2012/11", :posts => [] },
115
+ # # ...
116
+ # ]
117
+ # },
118
+ #
119
+ # # ...
120
+ # ]
121
+ # }
122
+ def archives
123
+ h = {}
124
+ h[:posts] = posts
125
+
126
+ # group posts by Date instances for the first day of the year
127
+ year_groups = posts.group_by { |post| Date.new(post.created.year) }.to_a
128
+
129
+ # collect all elements as maps for the year start date and the posts in that year
130
+ year_groups.map! do |year_start_date, posts_by_year|
131
+ {
132
+ :date => year_start_date,
133
+ :posts => posts_by_year.sort_by { |post| post.created }
134
+ }
135
+ end
136
+
137
+ year_groups.sort_by! { |year_hash| year_hash[:date] }
138
+ year_groups.reverse!
139
+
140
+ year_groups.each do |year_hash|
141
+ year_posts = year_hash[:posts]
142
+
143
+ # group the posts in the year by month
144
+ month_groups = year_posts.group_by { |post| Date.new(post.created.year, post.created.month) }.to_a
145
+
146
+ # collect the elements as maps for the month start date and the posts in that month
147
+ month_groups.map! do |month_start_date, posts_by_month|
148
+ {
149
+ :date => month_start_date,
150
+ :posts => posts_by_month.sort_by { |post| post.created },
151
+ :archive_url => archive_url_for_date(month_start_date)
152
+ }
153
+ end
154
+
155
+ month_groups.sort_by! { |month_hash| month_hash[:date] }
156
+ month_groups.reverse!
157
+
158
+ # set the months for the current year
159
+ year_hash[:months] = month_groups
160
+ end
161
+
162
+ h[:years] = year_groups
163
+
164
+ # return the final hash
165
+ h
166
+ end
167
+
168
+ def to_liquid
169
+ {
170
+ "posts" => posts,
171
+ "latest_update_time" => latest_update_time,
172
+ "archive" => self.class.stringify_keys(archives)
173
+ }
174
+ end
175
+
82
176
  def generate
83
177
  FileUtils.cd(@source_directory)
84
178
 
@@ -88,7 +182,7 @@ class Site
88
182
  files = Dir["**/*"].select { |f| f !~ /\A_/ && File.file?(f) }
89
183
 
90
184
  layout = Liquid::Template.parse(File.read("_layouts/default.html"))
91
- posts = self.posts.sort_by { |entry| entry.created }.reverse
185
+ posts = self.posts
92
186
 
93
187
  files.each do |path|
94
188
  puts "Processing file: #{path}"
@@ -119,9 +213,9 @@ class Site
119
213
  end
120
214
 
121
215
  if layout_option == "none"
122
- f.puts Liquid::Template.parse(file.to_s).render!("site" => { "posts" => posts, "latest_update_time" => latest_update_time })
216
+ f.puts Liquid::Template.parse(file.to_s).render!("site" => self)
123
217
  else
124
- f.puts layout.render!("page" => { "title" => [title].compact }, "content" => Liquid::Template.parse(file.to_s).render!("site" => { "posts" => posts, "latest_update_time" => latest_update_time }))
218
+ f.puts layout.render!("page" => { "title" => [title].compact }, "content" => Liquid::Template.parse(file.to_s).render!("site" => self))
125
219
  end
126
220
  end
127
221
  end
@@ -137,6 +231,8 @@ class Site
137
231
  end
138
232
  end
139
233
 
234
+ generate_archives(layout)
235
+
140
236
  if Dir.exist?("_site")
141
237
  FileUtils.mv("_site", "/tmp/_site.#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}")
142
238
  end
@@ -144,5 +240,43 @@ class Site
144
240
  FileUtils.mv("tmp/_site", ".") && FileUtils.rm_rf("tmp/_site")
145
241
  FileUtils.rmdir("tmp")
146
242
  end
243
+
244
+ private
245
+
246
+ # Uses config.archive_url_format to generate pages
247
+ # using the archive_page.html template.
248
+ def generate_archives(layout)
249
+ return unless config.archive_enabled?
250
+
251
+ template = Liquid::Template.parse(File.read("_templates/archive_page.html"))
252
+
253
+ months = posts.group_by { |post| Date.new(post.created.year, post.created.month) }
254
+
255
+ months.each do |month, posts|
256
+ archive_path = tmp_path(archive_url_for_date(month))
257
+
258
+ FileUtils.mkdir_p(archive_path)
259
+
260
+ File.open(File.join(archive_path, "index.html"), "w") do |f|
261
+ f.puts layout.render!("content" => template.render!("site" => self, "month" => month, "posts" => posts))
262
+ end
263
+ end
264
+ end
265
+
266
+ def self.stringify_keys(obj)
267
+ return obj unless obj.is_a?(Hash) || obj.is_a?(Array)
268
+
269
+ if obj.is_a?(Array)
270
+ return obj.map do |el|
271
+ stringify_keys(el)
272
+ end
273
+ end
274
+
275
+ result = {}
276
+ obj.each do |key, value|
277
+ result[key.to_s] = stringify_keys(value)
278
+ end
279
+ result
280
+ end
147
281
  end
148
282
  end
data/serif.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "serif"
3
- s.version = "0.1.2"
3
+ s.version = "0.1.3"
4
4
  s.authors = ["Adam Prescott"]
5
5
  s.email = ["adam@aprescott.com"]
6
6
  s.homepage = "https://github.com/aprescott/serif"
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  [
16
16
  "rake", "~> 0.9",
17
17
  "rack", "~> 1.0",
18
- "yui-compressor", ">= 0",
18
+ "rack-rewrite", "~> 1.3.0",
19
19
  "redcarpet", "~> 2.2",
20
20
  "pygments.rb", "~> 0.3",
21
21
  "sinatra", "~> 1.3",
@@ -12,3 +12,7 @@
12
12
  admin:
13
13
  username: changethisusername
14
14
  password: changethispassword
15
+ permalink: /:title
16
+ archive:
17
+ enabled: yes
18
+ url_format: /archive/:year/:month
@@ -0,0 +1,3 @@
1
+ title: Sample draft
2
+
3
+ Just a sample draft.
@@ -0,0 +1,4 @@
1
+ title: Sample post
2
+ Created: 2012-11-21T17:07:09+00:00
3
+
4
+ Just a sample post.
@@ -0,0 +1,9 @@
1
+ <h1>{{ month | date: "%b %Y" }} ({{ posts | size }})</h1>
2
+
3
+ <ul>
4
+ {% for post in posts %}
5
+ <li>
6
+ <a href="{{ post.url }}">{{ post.title }}</a>
7
+ </li>
8
+ {% endfor %}
9
+ </ul>
@@ -0,0 +1,7 @@
1
+ {% for year in site.archive.years %}
2
+ {% for month in year.months %}
3
+ <h1>
4
+ <a href="{{ month.archive_url }}">{{ month.date | date: "%B %Y" }}</a>
5
+ </h1>
6
+ {% endfor %}
7
+ {% endfor %}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serif
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-11 00:00:00.000000000 Z
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -44,21 +44,21 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '1.0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: yui-compressor
47
+ name: rack-rewrite
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
- - - ! '>='
51
+ - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 1.3.0
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
- - - ! '>='
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 1.3.0
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: redcarpet
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -179,9 +179,13 @@ files:
179
179
  - statics/templates/admin/index.liquid
180
180
  - statics/templates/admin/edit_post.liquid
181
181
  - statics/skeleton/index.html
182
+ - statics/skeleton/_templates/archive_page.html
182
183
  - statics/skeleton/_templates/post.html
184
+ - statics/skeleton/_drafts/sample-draft
183
185
  - statics/skeleton/_config.yml
186
+ - statics/skeleton/_posts/2012-01-05-sample-post
184
187
  - statics/skeleton/_layouts/default.html
188
+ - statics/skeleton/archive.html
185
189
  - statics/assets/js/mousetrap.min.js
186
190
  - statics/assets/js/jquery.autosize.js
187
191
  - bin/serif