shinmun 0.1

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.
Files changed (74) hide show
  1. data/.gitignore +1 -0
  2. data/LICENSE +18 -0
  3. data/README.md +249 -0
  4. data/Rakefile +60 -0
  5. data/bin/shinmun +12 -0
  6. data/example/posts/2008/9/example.md +19 -0
  7. data/example/posts/blog.yml +10 -0
  8. data/example/posts/uuid.state +3 -0
  9. data/example/public/controllers/comments.php +56 -0
  10. data/example/public/images/loading.gif +0 -0
  11. data/example/public/javascripts/comments.js +60 -0
  12. data/example/public/javascripts/highlight.js +505 -0
  13. data/example/public/javascripts/images/bg-fill.png +0 -0
  14. data/example/public/javascripts/images/bg.png +0 -0
  15. data/example/public/javascripts/images/blockquote.png +0 -0
  16. data/example/public/javascripts/images/bold.png +0 -0
  17. data/example/public/javascripts/images/code.png +0 -0
  18. data/example/public/javascripts/images/h1.png +0 -0
  19. data/example/public/javascripts/images/hr.png +0 -0
  20. data/example/public/javascripts/images/img.png +0 -0
  21. data/example/public/javascripts/images/italic.png +0 -0
  22. data/example/public/javascripts/images/link.png +0 -0
  23. data/example/public/javascripts/images/ol.png +0 -0
  24. data/example/public/javascripts/images/redo.png +0 -0
  25. data/example/public/javascripts/images/separator.png +0 -0
  26. data/example/public/javascripts/images/ul.png +0 -0
  27. data/example/public/javascripts/images/undo.png +0 -0
  28. data/example/public/javascripts/images/wmd-on.png +0 -0
  29. data/example/public/javascripts/images/wmd.png +0 -0
  30. data/example/public/javascripts/jquery-form.js +869 -0
  31. data/example/public/javascripts/jquery.js +3383 -0
  32. data/example/public/javascripts/languages/1c.js +82 -0
  33. data/example/public/javascripts/languages/axapta.js +52 -0
  34. data/example/public/javascripts/languages/bash.js +80 -0
  35. data/example/public/javascripts/languages/diff.js +64 -0
  36. data/example/public/javascripts/languages/dos.js +33 -0
  37. data/example/public/javascripts/languages/dynamic.js +460 -0
  38. data/example/public/javascripts/languages/ini.js +36 -0
  39. data/example/public/javascripts/languages/javascript.js +38 -0
  40. data/example/public/javascripts/languages/lisp.js +86 -0
  41. data/example/public/javascripts/languages/mel.js +50 -0
  42. data/example/public/javascripts/languages/profile.js +50 -0
  43. data/example/public/javascripts/languages/renderman.js +71 -0
  44. data/example/public/javascripts/languages/smalltalk.js +53 -0
  45. data/example/public/javascripts/languages/sql.js +50 -0
  46. data/example/public/javascripts/languages/static.js +175 -0
  47. data/example/public/javascripts/languages/vbscript.js +25 -0
  48. data/example/public/javascripts/languages/www.js +245 -0
  49. data/example/public/javascripts/prettyDate.js +36 -0
  50. data/example/public/javascripts/showdown.js +421 -0
  51. data/example/public/javascripts/template.js +165 -0
  52. data/example/public/javascripts/wmd-base.js +1799 -0
  53. data/example/public/javascripts/wmd-plus.js +311 -0
  54. data/example/public/javascripts/wmd.js +73 -0
  55. data/example/public/stylesheets/grid.css +243 -0
  56. data/example/public/stylesheets/grid.png +0 -0
  57. data/example/public/stylesheets/highlight/ascetic.css +38 -0
  58. data/example/public/stylesheets/highlight/dark.css +96 -0
  59. data/example/public/stylesheets/highlight/default.css +91 -0
  60. data/example/public/stylesheets/highlight/far.css +95 -0
  61. data/example/public/stylesheets/highlight/idea.css +75 -0
  62. data/example/public/stylesheets/highlight/sunburst.css +112 -0
  63. data/example/public/stylesheets/highlight/zenburn.css +108 -0
  64. data/example/public/stylesheets/print.css +76 -0
  65. data/example/public/stylesheets/reset.css +45 -0
  66. data/example/public/stylesheets/style.css +141 -0
  67. data/example/public/stylesheets/typography.css +59 -0
  68. data/example/templates/feed.rxml +21 -0
  69. data/example/templates/layout.rhtml +54 -0
  70. data/example/templates/page.rhtml +4 -0
  71. data/example/templates/post.rhtml +57 -0
  72. data/example/templates/posts.rhtml +10 -0
  73. data/lib/shinmun.rb +420 -0
  74. metadata +151 -0
data/lib/shinmun.rb ADDED
@@ -0,0 +1,420 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'erb'
4
+ require 'yaml'
5
+ require 'uuid'
6
+ require 'bluecloth'
7
+ require 'rubypants'
8
+ require 'rexml/document'
9
+
10
+ # A small and beautiful blog engine.
11
+ module Shinmun
12
+
13
+ # strip html tags from string
14
+ def self.strip_tags(html)
15
+ REXML::Document.new(html).each_element( './/text()' ).join
16
+ end
17
+
18
+ def self.urlify(string)
19
+ string.downcase.gsub(/[ -]+/, '-').gsub(/[^-a-z0-9_]+/, '')
20
+ end
21
+
22
+
23
+ # This class represents an article or page.
24
+ # A post has a header and body text.
25
+ # Example:
26
+ # ---
27
+ # category: Ruby
28
+ # date: 2008-09-05
29
+ # guid: 7ad04f10-5dd6-012b-b53c-001a92975b89
30
+ #
31
+ # BlueCloth, a Markdown library
32
+ # =============================
33
+ #
34
+ # This is the summary, which is by definition the first paragraph of the
35
+ # article. The summary shows up in list views and rss feeds.
36
+ class Post
37
+
38
+ attr_reader :blog, :title, :path, :head, :src
39
+
40
+ # Split up the source text into header and body.
41
+ # Load the header as yaml document.
42
+ def initialize(blog, path)
43
+ @src = File.read(path)
44
+ @blog = blog
45
+ @path = path.chomp('.md')
46
+
47
+ case @src
48
+ when /---.*?\n(.*?)\n\n(.*?)\n.*?\n(.*)/m
49
+ @head = YAML.load($1)
50
+ @title = $2
51
+ @body = $3
52
+
53
+ when /(.*?)\n.*?\n(.*)/m
54
+ @head = {}
55
+ @title = $1
56
+ @body = $2
57
+ else
58
+ raise "This file is empty!"
59
+ end
60
+ end
61
+
62
+ # Generates the body from source text.
63
+ def body
64
+ @__body__ ||= RubyPants.new(BlueCloth.new(@body).to_html).to_html
65
+ end
66
+
67
+ # Generate an unique id.
68
+ def generate_guid
69
+ @head['guid'] = UUID.new.generate
70
+ end
71
+
72
+ def date ; @head['date'] end
73
+ def tags ; @head['tags'] end
74
+ def languages; @head['languages'] end
75
+ def category ; @head['category'] end
76
+ def guid ; @head['guid'] end
77
+ def year ; date.year end
78
+ def month ; date.month end
79
+
80
+ # Return the first paragraph of rendered html.
81
+ def summary
82
+ body.split("\n\n")[0]
83
+ end
84
+
85
+ # Return doc root as relative path.
86
+ def root
87
+ slashes = path.count('/')
88
+ if slashes > 0
89
+ Array.new(slashes, '..').join('/') + '/'
90
+ else
91
+ ''
92
+ end
93
+ end
94
+
95
+ # Return a hash of post attributes.
96
+ def variables
97
+ head.merge(:root => root,
98
+ :header => category || title,
99
+ :title => title,
100
+ :body => body,
101
+ :link => link)
102
+ end
103
+
104
+ # Return absolute link to this post.
105
+ def link
106
+ "#{blog.meta['blog_url']}/#{path}.html"
107
+ end
108
+
109
+ end
110
+
111
+
112
+ # This class renders an ERB template for a set of attributes, which
113
+ # are accessible as instance variables.
114
+ class Template
115
+
116
+ attr_reader :root
117
+
118
+ # Initialize this template with an ERB instance.
119
+ def initialize(erb)
120
+ @erb = erb
121
+ end
122
+
123
+ # Set instance variable for this template.
124
+ def set_variables(vars)
125
+ for name, value in vars
126
+ instance_variable_set("@#{name}", value)
127
+ end
128
+ end
129
+
130
+ # Render this template.
131
+ def render
132
+ @erb.result(binding)
133
+ end
134
+
135
+ # Render a hash as attributes for a HTML tag.
136
+ def attributes(attributes)
137
+ attributes.map { |k, v| %Q{#{k}="#{v}"} }.join(' ')
138
+ end
139
+
140
+ # Render a HTML tag with given name.
141
+ # The last argument specifies the attributes of the tag.
142
+ # The second argument may be the content of the tag.
143
+ def tag(name, *args)
144
+ text, attributes = args.first.is_a?(Hash) ? [nil, args.first] : args
145
+ "<#{name} #{attributes(attributes)}>#{text}</#{name}>"
146
+ end
147
+
148
+ # Render stylesheet link tags with fixed url.
149
+ def stylesheet_link_tag(*names)
150
+ names.map { |name|
151
+ mtime = File.mtime("public/stylesheets/#{name}.css").to_i
152
+ path = "#{root}stylesheets/#{name}.css?#{mtime}"
153
+ tag :link, :href => path, :rel => 'stylesheet', :media => 'screen'
154
+ }.join("\n")
155
+ end
156
+
157
+ # Render javascript tags with fixed url.
158
+ def javascript_tag(*names)
159
+ names.map { |name|
160
+ mtime = File.mtime("public/javascripts/#{name}.js").to_i
161
+ path = "#{root}javascripts/#{name}.js?#{mtime}"
162
+ tag :script, :src => path, :type => 'text/javascript'
163
+ }.join("\n")
164
+ end
165
+
166
+ # Render an image tag with fixed url.
167
+ def image_tag(src, options = {})
168
+ tag :img, options.merge(:src => root + 'images/' + src)
169
+ end
170
+
171
+ # Render a link with fixed url.
172
+ def link_to(text, path, options = {})
173
+ tag :a, text, options.merge(:href => root + path + '.html')
174
+ end
175
+
176
+ # Render a link for the navigation bar. If the text of the link
177
+ # matches the @header variable, the css class will be set to acitve.
178
+ def navi_link(text, path)
179
+ link_to text, path, :class => (text == @header) ? 'active' : nil
180
+ end
181
+
182
+ # Render a link to a post with fixed url.
183
+ def post_link(post)
184
+ link_to post.title, post.path
185
+ end
186
+
187
+ # Render a link to an archive page.
188
+ def month_link(year, month)
189
+ link_to "#{Date::MONTHNAMES[month]} #{year}", "#{year}/#{month}/index"
190
+ end
191
+
192
+ # Render a date or time in a nice human readable format.
193
+ def date(time)
194
+ "%s %d, %d" % [Date::MONTHNAMES[time.month], time.day, time.year]
195
+ end
196
+
197
+ # Render a date or time in rfc822 format. This will be used for rss rendering.
198
+ def rfc822(time)
199
+ time.strftime("%a, %d %b %Y %H:%M:%S %z")
200
+ end
201
+
202
+ def strip_tags(html)
203
+ Shinmun.strip_tags(html)
204
+ end
205
+
206
+ end
207
+
208
+
209
+ # This class represents a blog. You need to provide a source
210
+ # directory and the meta file `blog.yml` which defines some variables.
211
+ # Example for `blog.yml`:
212
+ # blog_title: Matthias Georgi
213
+ # blog_description: Webdev, Gamedev, Interaction Design
214
+ # blog_language: en
215
+ # blog_author: Matthias Georgi
216
+ # blog_url: http://www.matthias-georgi.de
217
+ # categories:
218
+ # - Ruby
219
+ # - Emacs
220
+ class Blog
221
+
222
+ attr_reader :meta, :posts, :pages
223
+
224
+ # Read all posts from disk.
225
+ def initialize
226
+ @uuid = UUID.new
227
+
228
+ Dir.chdir('posts') do
229
+ @meta = YAML.load(File.read('blog.yml'))
230
+
231
+ @posts, @pages = Dir['**/*.md'].
232
+ map { |path| Post.new(self, path) }.
233
+ partition {|p| p.date }
234
+
235
+ @posts = @posts.sort_by { |post| post.date }.reverse
236
+ end
237
+
238
+ @templates = {}
239
+ end
240
+
241
+ def categories
242
+ meta['categories']
243
+ end
244
+
245
+ def create_post(title)
246
+ date = Date.today
247
+ name = Shinmun.urlify(title)
248
+ file = "posts/#{date.year}/#{date.month}/#{name}.md"
249
+
250
+ if File.exist?(file)
251
+ raise "#{file} exists"
252
+ else
253
+ puts "creating #{file}"
254
+ head = {
255
+ 'date' => date,
256
+ 'guid' => @uuid.generate,
257
+ 'category' => ''
258
+ }
259
+ FileUtils.mkdir_p(File.dirname(file))
260
+ File.open(file, "w") do |io|
261
+ io.puts head.to_yaml
262
+ io.puts
263
+ io.puts title
264
+ io.puts "=" * title.size
265
+ io.puts
266
+ end
267
+ end
268
+ end
269
+
270
+ # Return template variables as hash.
271
+ def variables
272
+ meta.merge(:posts => posts,
273
+ :months => months,
274
+ :categories => categories)
275
+ end
276
+
277
+ # Read and cache template file.
278
+ def template(name)
279
+ @templates[name] ||= ERB.new(File.read("templates/#{name}"))
280
+ end
281
+
282
+ # Render template with given variables.
283
+ def render_template(name, vars)
284
+ template = Template.new(template(name))
285
+ template.set_variables(vars)
286
+ template.render
287
+ end
288
+
289
+ # Render template and insert into layout with given variables.
290
+ def render(name, vars)
291
+ vars = variables.merge(vars)
292
+ content = render_template(name, vars)
293
+ if name =~ /\.rxml$/
294
+ content
295
+ else
296
+ render_template("layout.rhtml", vars.merge(:content => content))
297
+ end
298
+ end
299
+
300
+ # Write a file to output directory.
301
+ def write_file(path, data)
302
+ FileUtils.mkdir_p('public/' + File.dirname(path))
303
+ open('public/' + path, 'wb') do |file|
304
+ file << data
305
+ end
306
+ end
307
+
308
+ # Render a template and write to file.
309
+ def render_file(path, name, vars)
310
+ puts path
311
+ write_file(path, render(name, vars))
312
+ end
313
+
314
+ # Return all posts for a given month.
315
+ def posts_for_month(year, month)
316
+ posts.select { |p| p.year == year and p.month == month }
317
+ end
318
+
319
+ # Return all posts for given tag.
320
+ def posts_for_tag(tag)
321
+ tag = tag.downcase
322
+ posts.select { |p| p.tags.to_s.match(tag) }
323
+ end
324
+
325
+ # Return all posts in given category.
326
+ def posts_for_category(category)
327
+ posts.select { |p| p.category == category }
328
+ end
329
+
330
+ # Return all months as tuples of [year, month].
331
+ def months
332
+ posts.map { |p| [p.year, p.month] }.uniq.sort
333
+ end
334
+
335
+ # Write all posts.
336
+ def write_posts
337
+ for post in posts
338
+ render_file("#{post.path}.html",
339
+ "post.rhtml",
340
+ post.variables)
341
+ end
342
+ end
343
+
344
+ # Write all pages.
345
+ def write_pages
346
+ for page in pages
347
+ render_file("#{page.path}.html",
348
+ "page.rhtml",
349
+ page.variables)
350
+ end
351
+ end
352
+
353
+ # Write archive summaries.
354
+ def write_archives
355
+ for year, month in months
356
+ path = "#{year}/#{month}"
357
+ month_name = Date::MONTHNAMES[month]
358
+ posts = posts_for_month(year, month)
359
+
360
+ render_file("#{path}/index.html",
361
+ "posts.rhtml",
362
+ :header => "#{month_name} #{year}",
363
+ :year => year,
364
+ :month => month_name,
365
+ :posts => posts,
366
+ :root => '../../')
367
+ end
368
+ end
369
+
370
+ # Write category summaries.
371
+ def write_categories
372
+ for category in categories
373
+ posts = posts_for_category(category)
374
+ render_file("categories/#{category.downcase}.html",
375
+ "posts.rhtml",
376
+ :header => category,
377
+ :category => category,
378
+ :posts => posts,
379
+ :root => '../')
380
+ end
381
+ end
382
+
383
+ # Write index page.
384
+ def write_index
385
+ render_file("index.html",
386
+ "posts.rhtml",
387
+ :header => 'Home',
388
+ :posts => posts[0, 10],
389
+ :root => '')
390
+ end
391
+
392
+ # Write rss feeds for index page and categories.
393
+ def write_feeds
394
+ render_file("index.rss",
395
+ "feed.rxml",
396
+ :posts => posts[0, 10],
397
+ :root => '')
398
+
399
+ for category in categories
400
+ posts = posts_for_category(category)
401
+ render_file("categories/#{category.downcase}.rss",
402
+ "feed.rxml",
403
+ :category => category,
404
+ :posts => posts,
405
+ :root => '../')
406
+ end
407
+ end
408
+
409
+ def write_all
410
+ write_posts
411
+ write_pages
412
+ write_archives
413
+ write_categories
414
+ write_index
415
+ write_feeds
416
+ end
417
+
418
+ end
419
+
420
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shinmun
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Georgi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-09-11 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: uuid
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: BlueCloth
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.0.0
32
+ version:
33
+ - !ruby/object:Gem::Dependency
34
+ name: rubypants
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
41
+ version:
42
+ description: Shinmun is a blog engine, which renders text files using a markup language like Markdown and a set of templates into static web pages. It supports Categories, Archives and RSS Feeds. Commenting can be done with some Javascript, PHP and a flat file JSON store.
43
+ email: matti.georgi@gmail.com
44
+ executables:
45
+ - shinmun
46
+ extensions: []
47
+
48
+ extra_rdoc_files:
49
+ - README.md
50
+ files:
51
+ - .gitignore
52
+ - LICENSE
53
+ - README.md
54
+ - Rakefile
55
+ - bin/shinmun
56
+ - example/posts/2008/9/example.md
57
+ - example/posts/blog.yml
58
+ - example/posts/uuid.state
59
+ - example/public/controllers/comments.php
60
+ - example/public/images/loading.gif
61
+ - example/public/javascripts/comments.js
62
+ - example/public/javascripts/highlight.js
63
+ - example/public/javascripts/images/bg-fill.png
64
+ - example/public/javascripts/images/bg.png
65
+ - example/public/javascripts/images/blockquote.png
66
+ - example/public/javascripts/images/bold.png
67
+ - example/public/javascripts/images/code.png
68
+ - example/public/javascripts/images/h1.png
69
+ - example/public/javascripts/images/hr.png
70
+ - example/public/javascripts/images/img.png
71
+ - example/public/javascripts/images/italic.png
72
+ - example/public/javascripts/images/link.png
73
+ - example/public/javascripts/images/ol.png
74
+ - example/public/javascripts/images/redo.png
75
+ - example/public/javascripts/images/separator.png
76
+ - example/public/javascripts/images/ul.png
77
+ - example/public/javascripts/images/undo.png
78
+ - example/public/javascripts/images/wmd-on.png
79
+ - example/public/javascripts/images/wmd.png
80
+ - example/public/javascripts/jquery-form.js
81
+ - example/public/javascripts/jquery.js
82
+ - example/public/javascripts/languages/1c.js
83
+ - example/public/javascripts/languages/axapta.js
84
+ - example/public/javascripts/languages/bash.js
85
+ - example/public/javascripts/languages/diff.js
86
+ - example/public/javascripts/languages/dos.js
87
+ - example/public/javascripts/languages/dynamic.js
88
+ - example/public/javascripts/languages/ini.js
89
+ - example/public/javascripts/languages/javascript.js
90
+ - example/public/javascripts/languages/lisp.js
91
+ - example/public/javascripts/languages/mel.js
92
+ - example/public/javascripts/languages/profile.js
93
+ - example/public/javascripts/languages/renderman.js
94
+ - example/public/javascripts/languages/smalltalk.js
95
+ - example/public/javascripts/languages/sql.js
96
+ - example/public/javascripts/languages/static.js
97
+ - example/public/javascripts/languages/vbscript.js
98
+ - example/public/javascripts/languages/www.js
99
+ - example/public/javascripts/prettyDate.js
100
+ - example/public/javascripts/showdown.js
101
+ - example/public/javascripts/template.js
102
+ - example/public/javascripts/wmd-base.js
103
+ - example/public/javascripts/wmd-plus.js
104
+ - example/public/javascripts/wmd.js
105
+ - example/public/stylesheets/grid.css
106
+ - example/public/stylesheets/grid.png
107
+ - example/public/stylesheets/highlight/ascetic.css
108
+ - example/public/stylesheets/highlight/dark.css
109
+ - example/public/stylesheets/highlight/default.css
110
+ - example/public/stylesheets/highlight/far.css
111
+ - example/public/stylesheets/highlight/idea.css
112
+ - example/public/stylesheets/highlight/sunburst.css
113
+ - example/public/stylesheets/highlight/zenburn.css
114
+ - example/public/stylesheets/print.css
115
+ - example/public/stylesheets/reset.css
116
+ - example/public/stylesheets/style.css
117
+ - example/public/stylesheets/typography.css
118
+ - example/templates/feed.rxml
119
+ - example/templates/layout.rhtml
120
+ - example/templates/page.rhtml
121
+ - example/templates/post.rhtml
122
+ - example/templates/posts.rhtml
123
+ - lib/shinmun.rb
124
+ has_rdoc: true
125
+ homepage: http://shinmun.rubyforge.org
126
+ post_install_message:
127
+ rdoc_options: []
128
+
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: "0"
136
+ version:
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: "0"
142
+ version:
143
+ requirements: []
144
+
145
+ rubyforge_project: shinmun
146
+ rubygems_version: 1.1.1
147
+ signing_key:
148
+ specification_version: 2
149
+ summary: a small blog engine
150
+ test_files: []
151
+