shinmun 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+