serif 0.1.2 → 0.1.3

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/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