shinmun 0.1 → 0.2

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 (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'