shinmun 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +127 -62
  3. data/Rakefile +23 -11
  4. data/bin/shinmun +6 -1
  5. data/lib/shinmun/admin_controller.rb +161 -0
  6. data/lib/shinmun/aggregations/delicious.rb +57 -0
  7. data/lib/shinmun/aggregations/flickr.rb +81 -0
  8. data/lib/shinmun/blog.rb +319 -0
  9. data/lib/shinmun/cache.rb +59 -0
  10. data/lib/shinmun/comment.rb +44 -0
  11. data/lib/shinmun/controller.rb +135 -0
  12. data/lib/shinmun/helpers.rb +116 -0
  13. data/lib/shinmun/post.rb +166 -0
  14. data/lib/shinmun/template.rb +39 -0
  15. data/lib/shinmun.rb +14 -411
  16. metadata +15 -81
  17. data/example/posts/2008/9/example.md +0 -19
  18. data/example/posts/blog.yml +0 -10
  19. data/example/posts/uuid.state +0 -3
  20. data/example/public/controllers/comments.php +0 -56
  21. data/example/public/images/loading.gif +0 -0
  22. data/example/public/javascripts/comments.js +0 -60
  23. data/example/public/javascripts/highlight.js +0 -505
  24. data/example/public/javascripts/images/bg-fill.png +0 -0
  25. data/example/public/javascripts/images/bg.png +0 -0
  26. data/example/public/javascripts/images/blockquote.png +0 -0
  27. data/example/public/javascripts/images/bold.png +0 -0
  28. data/example/public/javascripts/images/code.png +0 -0
  29. data/example/public/javascripts/images/h1.png +0 -0
  30. data/example/public/javascripts/images/hr.png +0 -0
  31. data/example/public/javascripts/images/img.png +0 -0
  32. data/example/public/javascripts/images/italic.png +0 -0
  33. data/example/public/javascripts/images/link.png +0 -0
  34. data/example/public/javascripts/images/ol.png +0 -0
  35. data/example/public/javascripts/images/redo.png +0 -0
  36. data/example/public/javascripts/images/separator.png +0 -0
  37. data/example/public/javascripts/images/ul.png +0 -0
  38. data/example/public/javascripts/images/undo.png +0 -0
  39. data/example/public/javascripts/images/wmd-on.png +0 -0
  40. data/example/public/javascripts/images/wmd.png +0 -0
  41. data/example/public/javascripts/jquery-form.js +0 -869
  42. data/example/public/javascripts/jquery.js +0 -3383
  43. data/example/public/javascripts/languages/1c.js +0 -82
  44. data/example/public/javascripts/languages/axapta.js +0 -52
  45. data/example/public/javascripts/languages/bash.js +0 -80
  46. data/example/public/javascripts/languages/diff.js +0 -64
  47. data/example/public/javascripts/languages/dos.js +0 -33
  48. data/example/public/javascripts/languages/dynamic.js +0 -460
  49. data/example/public/javascripts/languages/ini.js +0 -36
  50. data/example/public/javascripts/languages/javascript.js +0 -38
  51. data/example/public/javascripts/languages/lisp.js +0 -86
  52. data/example/public/javascripts/languages/mel.js +0 -50
  53. data/example/public/javascripts/languages/profile.js +0 -50
  54. data/example/public/javascripts/languages/renderman.js +0 -71
  55. data/example/public/javascripts/languages/smalltalk.js +0 -53
  56. data/example/public/javascripts/languages/sql.js +0 -50
  57. data/example/public/javascripts/languages/static.js +0 -175
  58. data/example/public/javascripts/languages/vbscript.js +0 -25
  59. data/example/public/javascripts/languages/www.js +0 -245
  60. data/example/public/javascripts/prettyDate.js +0 -36
  61. data/example/public/javascripts/showdown.js +0 -421
  62. data/example/public/javascripts/template.js +0 -165
  63. data/example/public/javascripts/wmd-base.js +0 -1799
  64. data/example/public/javascripts/wmd-plus.js +0 -311
  65. data/example/public/javascripts/wmd.js +0 -73
  66. data/example/public/stylesheets/grid.css +0 -243
  67. data/example/public/stylesheets/grid.png +0 -0
  68. data/example/public/stylesheets/highlight/ascetic.css +0 -38
  69. data/example/public/stylesheets/highlight/dark.css +0 -96
  70. data/example/public/stylesheets/highlight/default.css +0 -91
  71. data/example/public/stylesheets/highlight/far.css +0 -95
  72. data/example/public/stylesheets/highlight/idea.css +0 -75
  73. data/example/public/stylesheets/highlight/sunburst.css +0 -112
  74. data/example/public/stylesheets/highlight/zenburn.css +0 -108
  75. data/example/public/stylesheets/print.css +0 -76
  76. data/example/public/stylesheets/reset.css +0 -45
  77. data/example/public/stylesheets/style.css +0 -141
  78. data/example/public/stylesheets/typography.css +0 -59
  79. data/example/templates/feed.rxml +0 -21
  80. data/example/templates/layout.rhtml +0 -54
  81. data/example/templates/page.rhtml +0 -4
  82. data/example/templates/post.rhtml +0 -57
  83. data/example/templates/posts.rhtml +0 -10
@@ -0,0 +1,166 @@
1
+ module Shinmun
2
+
3
+ # This class represents a post or page.
4
+ # Each post has a header, encoded as YAML and a body.
5
+ #
6
+ # Example:
7
+ # ---
8
+ # category: Ruby
9
+ # date: 2008-09-05
10
+ #
11
+ # BlueCloth, a Markdown library
12
+ # =============================
13
+ #
14
+ # This is the summary, which is by definition the first paragraph of the
15
+ # article. The summary shows up in list views and rss feeds.
16
+ #
17
+ class Post
18
+
19
+ # Define accessor methods for head variable.
20
+ def self.head_accessor(*names)
21
+ names.each do |name|
22
+ name = name.to_s
23
+ define_method(name) { @head[name] }
24
+ define_method("#{name}=") {|v| @head[name] = v }
25
+ end
26
+ end
27
+
28
+ attr_accessor :prefix, :path, :type, :title, :head, :body, :summary, :body_html
29
+ head_accessor :author, :date, :category, :tags, :languages, :header
30
+
31
+ # Initialize empty post and set specified attributes.
32
+ def initialize(attributes={})
33
+ @head = {}
34
+ @body = ''
35
+
36
+ for k, v in attributes
37
+ send "#{k}=", v
38
+ end
39
+ end
40
+
41
+ # Shortcut for year of date
42
+ def year
43
+ date.year
44
+ end
45
+
46
+ # Shortcut for month of date
47
+ def month
48
+ date.month
49
+ end
50
+
51
+ def filename
52
+ "#{prefix}/#{path}.#{type}"
53
+ end
54
+
55
+ # Strips off extension and prefix.
56
+ def filename=(file)
57
+ if match = file.match(/^(.*?)\/(.*)\.(.*)/)
58
+ @prefix = match[1]
59
+ @path = match[2]
60
+ @type = match[3]
61
+ else
62
+ raise "incorrect filename: #{file}"
63
+ end
64
+ end
65
+
66
+ # Split up the source into header and body. Load the header as
67
+ # yaml document. Render body and parse the summary from rendered html.
68
+ def parse(src)
69
+ # Parse YAML header if present
70
+ if src =~ /---.*?\n(.*?)\n\n(.*)/m
71
+ @head = YAML.load($1)
72
+ @body = $2
73
+ else
74
+ @body = src
75
+ end
76
+
77
+ @title = head['title'] or parse_title
78
+ @body_html = transform(body, type)
79
+ @summary = body_html.split("\n\n")[0]
80
+
81
+ self
82
+ end
83
+
84
+ # Parse title from different formats
85
+ def parse_title
86
+ lines = body.split("\n")
87
+
88
+ case type
89
+ when 'md'
90
+ @title = lines.shift.sub(/(^#+|#+$)/,'').strip
91
+ lines.shift if lines.first.match(/^(=|-)+$/)
92
+
93
+ when 'html'
94
+ @title = lines.shift.sub(/(<h1>|\<\/h1>)/,'').strip
95
+
96
+ when 'tt'
97
+ @title = lines.shift.sub(/(^h1.)/,'').strip
98
+ end
99
+
100
+ @body = lines.join("\n")
101
+ end
102
+
103
+ # Convert to yaml for caching.
104
+ def to_yaml
105
+ head.merge('author' => author,
106
+ 'path' => path,
107
+ 'type' => type,
108
+ 'title' => title,
109
+ 'summary' => summary,
110
+ 'body_html' => body_html).to_yaml
111
+ end
112
+
113
+ # Convert to string representation, used to create new posts.
114
+ def dump
115
+ head = self.head.dup
116
+ body = self.body.dup
117
+
118
+ if type == 'md'
119
+ body = title + "\n" + ("=" * title.size) + "\n\n" + body
120
+ end
121
+
122
+ head.each do |k, v|
123
+ head.delete(k) if v.nil? || v.empty?
124
+ end
125
+
126
+ if head.empty?
127
+ body
128
+ else
129
+ head.to_yaml + "\n\n" + body
130
+ end
131
+ end
132
+
133
+ def load
134
+ parse(File.read(filename))
135
+ end
136
+
137
+ def save
138
+ FileUtils.mkdir_p(File.dirname(filename))
139
+ File.open(filename, "w") { |io| io << dump }
140
+ self
141
+ end
142
+
143
+ # Variables used for templates.
144
+ def variables
145
+ head.merge(:author => author,
146
+ :path => path,
147
+ :title => title,
148
+ :body => body_html)
149
+ end
150
+
151
+ # Transform the body of this post according to given type.
152
+ # Defaults to Markdown.
153
+ def transform(src, type)
154
+ case type
155
+ when 'html'
156
+ RubyPants.new(src).to_html
157
+ when 'tt'
158
+ RubyPants.new(RedCloth.new(src).to_html).to_html
159
+ else
160
+ RubyPants.new(BlueCloth.new(src).to_html).to_html
161
+ end
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,39 @@
1
+ module Shinmun
2
+
3
+ module Helpers
4
+ end
5
+
6
+ # This class renders an ERB template for a set of attributes, which
7
+ # are accessible as instance variables.
8
+ class Template
9
+ include Helpers
10
+
11
+ attr_reader :erb, :blog
12
+
13
+ # Initialize this template with an ERB instance.
14
+ def initialize(erb, file)
15
+ @erb = erb
16
+ @file = file
17
+ end
18
+
19
+ # Set instance variable for this template.
20
+ def set_variables(vars)
21
+ for name, value in vars
22
+ instance_variable_set("@#{name}", value)
23
+ end
24
+ self
25
+ end
26
+
27
+ # Render this template.
28
+ def render
29
+ @erb.result(binding)
30
+ rescue => e
31
+ e.backtrace.each do |s|
32
+ s.gsub!('(erb)', @file)
33
+ end
34
+ raise e
35
+ end
36
+
37
+ end
38
+
39
+ end
data/lib/shinmun.rb CHANGED
@@ -2,419 +2,22 @@ require 'rubygems'
2
2
  require 'fileutils'
3
3
  require 'erb'
4
4
  require 'yaml'
5
- require 'uuid'
5
+ require 'time'
6
+
6
7
  require 'bluecloth'
7
8
  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
9
 
409
- def write_all
410
- write_posts
411
- write_pages
412
- write_archives
413
- write_categories
414
- write_index
415
- write_feeds
416
- end
10
+ begin; require 'redcloth'; rescue LoadError; end
11
+ begin; require 'packr'; rescue LoadError; end
417
12
 
418
- end
13
+ require 'shinmun/cache'
14
+ require 'shinmun/post'
15
+ require 'shinmun/comment'
16
+ require 'shinmun/template'
17
+ require 'shinmun/helpers'
18
+ require 'shinmun/blog'
419
19
 
420
- end
20
+ require 'shinmun/aggregations/audioscrobbler'
21
+ require 'shinmun/aggregations/delicious'
22
+ require 'shinmun/aggregations/flickr'
23
+ require 'shinmun/aggregations/fortythree'