shinmun 0.5.2 → 0.9

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/.gems ADDED
@@ -0,0 +1 @@
1
+ shinmun --version 0.9
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Matthias Georgi <http://www.matthias-georgi.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/shinmun CHANGED
@@ -9,26 +9,23 @@ when 'init'
9
9
  Shinmun::Blog.init ARGV[1]
10
10
 
11
11
  when 'post'
12
- blog = Shinmun::Blog.new('.')
13
- post = blog.create_post(:title => ARGV[1], :date => Date.today)
14
- path = blog.post_file(post)
15
-
16
- `git checkout master posts`
17
-
18
- exec "#{ENV['EDITOR']} #{path}"
12
+ post = Shinmun::Post.new(:title => ARGV[1], :date => Date.today)
13
+ post.save
19
14
 
20
- when 'page'
21
- blog = Shinmun::Blog.new('.')
22
- post = blog.create_page(:title => ARGV[1])
23
- path = blog.post_file(post)
15
+ puts "Created post '#{post.path}'"
24
16
 
25
- `git checkout master pages`
17
+ when 'page'
18
+ post = Shinmun::Post.new(:title => ARGV[1])
19
+ post.save
26
20
 
27
- exec "#{ENV['EDITOR']} #{path}"
21
+ puts "Created page '#{post.path}'"
28
22
 
29
23
  else
30
24
  puts "Usage:"
31
25
  puts " shinmun init dir - creates a new blog"
32
26
  puts " shinmun post 'Title of the post' - create a new post"
33
27
  puts " shinmun page 'Title of the page' - create a new page"
28
+ exit
34
29
  end
30
+
31
+
data/lib/shinmun/blog.rb CHANGED
@@ -4,7 +4,8 @@ module Shinmun
4
4
  class Blog < Kontrol::Application
5
5
  include Helpers
6
6
 
7
- attr_accessor :config, :store, :posts, :pages
7
+ attr_accessor :config
8
+ attr_reader :posts, :pages, :posts_by_date, :posts_by_category, :posts_by_tag
8
9
 
9
10
  %w[ base_path title description language author categories ].each do |name|
10
11
  define_method(name) { @config[name.to_sym] }
@@ -15,163 +16,109 @@ module Shinmun
15
16
  super
16
17
 
17
18
  @config = {}
18
- @store = GitStore.new(path)
19
- @store.handler['md'] = PostHandler.new
20
- @store.handler['rhtml'] = ERBHandler.new
21
- @store.handler['rxml'] = ERBHandler.new
19
+ @templates = {}
20
+
21
+ load_posts
22
+ load_pages
23
+ sort_posts
22
24
  end
23
25
 
24
26
  def self.init(path)
25
27
  path = File.expand_path(path)
26
28
  Dir.mkdir(path)
27
29
 
28
- FileUtils.cp_r "#{ROOT}/assets", path
30
+ FileUtils.cp_r "#{ROOT}/public", path
29
31
  FileUtils.cp_r "#{ROOT}/templates", path
30
32
  FileUtils.cp "#{ROOT}/config.ru", path
33
+ FileUtils.cp "#{ROOT}/.gems", path
31
34
 
32
35
  Dir.mkdir("#{path}/posts")
33
36
  Dir.mkdir("#{path}/pages")
34
- Dir.mkdir("#{path}/comments")
35
- Dir.mkdir("#{path}/public")
36
-
37
- FileUtils.ln_s("../assets", "#{path}/public/assets")
38
-
39
- Dir.chdir(path) do
40
- `git init`
41
- `git add .`
42
- `git commit -m 'init'`
43
- end
44
- end
45
-
46
- def load_template(file)
47
- store['templates/' + file]
48
37
  end
49
38
 
50
39
  def render(name, vars = {})
51
40
  super(name, vars.merge(:blog => self))
52
41
  end
53
42
 
54
- def pages
55
- store.tree('pages').values.select { |v| Post === v }
56
- end
43
+ def reload_changed_files
44
+ (@posts + @pages.values).each do |post|
45
+ if post.changed?
46
+ post.load
47
+ @changed = true
48
+ end
49
+ end
57
50
 
58
- def posts
59
- store.tree('posts').values.select { |v| Post === v }.sort_by { |p| p.date.to_s }.reverse
51
+ sort_posts if @changed
60
52
  end
61
53
 
62
54
  def call(env)
63
- if ENV['RACK_ENV'] == 'production'
64
- store.load if store.changed?
65
- else
66
- store.load(true)
67
- end
68
-
55
+ reload_changed_files # if ENV['RACK_ENV'] != 'production'
69
56
  super
70
57
  end
71
58
 
72
- def url
73
- "http://#{request.host}"
74
- end
59
+ def load_pages
60
+ @pages = {}
75
61
 
76
- def symbolize_keys(hash)
77
- hash.inject({}) do |h, (k, v)|
78
- h[k.to_sym] = v
79
- h
62
+ Dir["#{ path }/pages/*.md"].each do |file|
63
+ page = Post.new(:file => file)
64
+ @pages[page.name] = page
80
65
  end
81
66
  end
82
67
 
83
- def transaction(message, &block)
84
- store.transaction(message, &block)
85
- end
86
-
87
- def post_file(post)
88
- 'posts' + post_path(post) + '.' + post.type
89
- end
90
-
91
- def page_file(post)
92
- 'pages' + page_path(post) + '.' + post.type
93
- end
94
-
95
- def comment_file(post)
96
- 'comments/' + post_path(post) + '.yml'
97
- end
98
-
99
- def create_post(attr)
100
- post = Post.new(attr)
101
- path = post_file(post)
102
-
103
- transaction "create post `#{post.title}'" do
104
- store[path] = post
68
+ def load_posts
69
+ @posts = Dir["#{ path }/posts/**/*.md"].map do |file|
70
+ Post.new(:file => file)
105
71
  end
106
-
107
- post
108
72
  end
109
-
110
- def create_page(attr)
111
- post = Post.new(attr)
112
- path = page_file(post)
113
-
114
- transaction "create page `#{post.title}'" do
115
- store[path] = post
73
+
74
+ def sort_posts
75
+ @posts = @posts.sort_by { |post| post.date.to_s }.reverse
76
+
77
+ @posts_by_category = Hash.new do |hash, category|
78
+ hash[category] = []
116
79
  end
117
-
118
- post
119
- end
120
-
121
- def comments_for(post)
122
- store[comment_file post] || []
123
- end
124
-
125
- def create_comment(post, params)
126
- path = comment_file(post)
127
- comments = comments_for(post)
128
- comment = Comment.new(params)
129
80
 
130
- transaction "new comment for `#{post.title}'" do
131
- store[path] = comments + [comment]
81
+ @posts_by_tag = Hash.new do |hash, tag|
82
+ hash[tag] = []
83
+ end
84
+
85
+ @posts_by_date = Hash.new do |hash, year|
86
+ hash[year] = Hash.new do |hash_months, month|
87
+ hash_months[month] = Hash.new
88
+ end
132
89
  end
133
90
 
134
- comment
91
+ @posts.each do |post|
92
+ post.tag_list.each { |tag| @posts_by_tag[tag] << post }
93
+ @posts_by_category[post.category] << post if post.category
94
+ @posts_by_date[post.year][post.month][post.name] = post
95
+ end
135
96
  end
136
97
 
137
- def find_page(name)
138
- pages.find { |p| p.name == name }
98
+ def url
99
+ "http://#{request.host}"
139
100
  end
140
101
 
141
- def find_post(year, month, name)
142
- posts.find { |p| p.year == year and p.month == month and p.name == name }
102
+ def symbolize_keys(hash)
103
+ hash.inject({}) do |h, (k, v)|
104
+ h[k.to_sym] = v
105
+ h
106
+ end
143
107
  end
144
108
 
145
109
  def find_category(permalink)
146
- name = categories.find { |name| urlify(name) == permalink }
147
-
148
- { :name => name,
149
- :posts => posts.select { |p| p.category == name },
150
- :permalink => permalink }
151
- end
152
-
153
- def recent_posts
154
- posts[0, 20]
155
- end
156
-
157
- # Return all posts for a given month.
158
- def posts_for_month(year, month)
159
- posts.select { |p| p.year == year and p.month == month }
110
+ categories.find { |name| urlify(name) == permalink }
160
111
  end
161
112
 
162
113
  # Return all posts with any of given tags.
163
114
  def posts_with_tags(tags)
164
115
  return [] if tags.nil? or tags.empty?
165
116
  tags = tags.split(',').map { |t| t.strip } if tags.is_a?(String)
166
-
167
- posts.select do |post|
168
- tags.any? do |tag|
169
- post.tag_list.include?(tag)
170
- end
171
- end
117
+
118
+ tags.map { |tag| posts_by_tag[tag] }.flatten.uniq
172
119
  end
173
120
 
174
- # Return all archives as tuples of [year, month].
121
+ # Return all archives as tuples of [year, month, posts].
175
122
  def archives
176
123
  posts.map { |p| [p.year, p.month] }.uniq.sort
177
124
  end
@@ -2,43 +2,14 @@ module Shinmun
2
2
 
3
3
  module Helpers
4
4
 
5
- # taken form ActionView::Helpers
6
- def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
7
- from_time = from_time.to_time if from_time.respond_to?(:to_time)
8
- to_time = to_time.to_time if to_time.respond_to?(:to_time)
9
- distance_in_minutes = (((to_time - from_time).abs)/60).round
10
- distance_in_seconds = ((to_time - from_time).abs).round
11
-
12
- case distance_in_minutes
13
- when 0..1
14
- return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
15
- case distance_in_seconds
16
- when 0..4 then 'less than 5 seconds'
17
- when 5..9 then 'less than 10 seconds'
18
- when 10..19 then 'less than 20 seconds'
19
- when 20..39 then 'half a minute'
20
- when 40..59 then 'less than a minute'
21
- else '1 minute'
22
- end
23
-
24
- when 2..44 then "#{distance_in_minutes} minutes"
25
- when 45..89 then 'about 1 hour'
26
- when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
27
- when 1440..2879 then '1 day'
28
- when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
29
- when 43200..86399 then 'about 1 month'
30
- when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
31
- when 525600..1051199 then 'about 1 year'
32
- else "over #{(distance_in_minutes / 525600).round} years"
33
- end
34
- end
5
+ Kontrol::Template.send(:include, self)
35
6
 
36
7
  def post_path(post)
37
- "#{base_path}/#{post.year}/#{post.month}/#{post.name}"
8
+ "/#{post.year}/#{post.month}/#{post.name}"
38
9
  end
39
10
 
40
11
  def archive_path(year, month)
41
- "#{base_path}/#{year}/#{month}"
12
+ "/#{year}/#{month}"
42
13
  end
43
14
 
44
15
  # Render a link to a post
@@ -61,10 +32,16 @@ module Shinmun
61
32
  time.strftime("%a, %d %b %Y %H:%M:%S %z")
62
33
  end
63
34
 
64
- # Render a link for the navigation bar. If the text of the link
65
- # matches the @header variable, the css class will be set to acitve.
35
+ # Render a link for the navigation bar.
66
36
  def navi_link(text, path)
67
- link_to text, path, :class => (request.path_info == path) ? 'active' : nil
37
+ if path.match(/categories\/(.*)/)
38
+ active = $1 == urlify(@category) if @category
39
+ active = $1 == urlify(@post.category) if @post
40
+ else
41
+ active = request.path_info == path
42
+ end
43
+
44
+ link_to text, path, :class => active ? 'active' : nil
68
45
  end
69
46
 
70
47
  def html_escape(s)
data/lib/shinmun/post.rb CHANGED
@@ -19,25 +19,24 @@ module Shinmun
19
19
  define_method("#{name}=") {|v| head[name] = v }
20
20
  end
21
21
 
22
- attr_accessor :dirname, :name, :type, :src, :head, :body, :summary, :body_html, :tag_list
22
+ attr_writer :name
23
+ attr_accessor :src, :type, :head, :body, :file, :mtime
23
24
 
24
25
  # Initialize empty post and set specified attributes.
25
26
  def initialize(attributes={})
26
27
  @head = {}
27
28
  @body = ''
29
+ @type = 'md'
28
30
 
29
31
  attributes.each do |k, v|
30
32
  send "#{k}=", v
31
33
  end
32
-
33
- @type ||= 'md'
34
34
 
35
- parse(src) if src
35
+ load if file
36
+ end
36
37
 
37
- raise "post without a title" if title.nil?
38
-
39
- @name ||= title.downcase.gsub(/[ -]+/, '-').gsub(/[^-a-z0-9_]+/, '')
40
- @dirname = date ? "posts/#{year}/#{month}" : 'pages'
38
+ def name
39
+ @name ||= title.to_s.downcase.gsub(/[ -]+/, '-').gsub(/[^-a-z0-9_]+/, '')
41
40
  end
42
41
 
43
42
  def method_missing(id, *args)
@@ -49,10 +48,6 @@ module Shinmun
49
48
  end
50
49
  end
51
50
 
52
- def date=(date)
53
- @head['date'] = String === date ? Date.parse(date) : date
54
- end
55
-
56
51
  # Shortcut for year of date
57
52
  def year
58
53
  date.year
@@ -63,22 +58,39 @@ module Shinmun
63
58
  date.month
64
59
  end
65
60
 
66
- def filename
67
- "#{name}.#{type}"
61
+ def tag_list
62
+ @tag_list ||= tags.to_s.split(",").map { |s| s.strip }
68
63
  end
69
64
 
70
- def filename=(filename)
71
- self.name, self.type = filename.split('.')
65
+ def body_html
66
+ @body_html ||= transform(@body)
67
+ end
68
+
69
+ def summary
70
+ @summary ||= body_html.split("\n\n")[0]
72
71
  end
73
72
 
74
73
  def path
75
- dirname.to_s.empty? ? filename : "#{dirname}/#{filename}"
74
+ folder = date ? "posts/#{year}/#{month}" : 'pages'
75
+ "#{folder}/#{name}.#{type}"
76
76
  end
77
77
 
78
- def path=(path)
79
- list = path.split('/')
80
- self.dirname = list[0..-2].join('/')
81
- self.filename = list[-1]
78
+ def load
79
+ self.type = File.extname(file)[1..-1]
80
+ self.name = File.basename(file).chomp(".#{type}")
81
+ self.mtime = File.mtime(file)
82
+
83
+ parse(File.read(file))
84
+ end
85
+
86
+ def changed?
87
+ File.mtime(file) != mtime
88
+ end
89
+
90
+ def save
91
+ File.open(path, 'w') do |io|
92
+ io << dump
93
+ end
82
94
  end
83
95
 
84
96
  # Split up the source into header and body. Load the header as
@@ -88,15 +100,12 @@ module Shinmun
88
100
  @head = YAML.load($1)
89
101
  @body = $2
90
102
  else
91
- raise ArgumentError, "yaml header not found in src"
103
+ @body = src
92
104
  end
93
-
94
- @body_html = transform(@body)
95
- @summary = body_html.split("\n\n")[0]
96
- @tag_list = tags.to_s.split(",").map { |s| s.strip }
97
- @dirname = date ? "posts/#{year}/#{month}" : 'pages'
98
-
99
- self
105
+
106
+ @body_html = nil
107
+ @tag_list = nil
108
+ @summary = nil
100
109
  end
101
110
 
102
111
  # Convert to string representation
@@ -121,13 +130,7 @@ module Shinmun
121
130
  end
122
131
 
123
132
  def ==(obj)
124
- if Post === obj
125
- if date
126
- year == obj.year and month == obj.month and name == obj.name
127
- else
128
- name == obj.name
129
- end
130
- end
133
+ Post === obj and file == obj.file
131
134
  end
132
135
 
133
136
  end
@@ -1,39 +1,28 @@
1
1
  Shinmun::Blog.map do
2
-
3
- category_feed '/categories/(.*)\.rss' do |category|
4
- render 'category.rxml', find_category(category).merge(:layout => false)
5
- end
6
2
 
7
- category '/categories/(.*)' do |category|
8
- render 'category.rhtml', find_category(category)
3
+ category '/categories/(.*)' do |name|
4
+ category = find_category(name)
5
+
6
+ render 'category.rhtml', :category => category, :posts => posts_by_category[category]
9
7
  end
10
8
 
11
9
  tag '/tags/(.*)' do |tag|
12
- render 'category.rhtml', :name => "Tag: #{tag}", :posts => posts_with_tags(tag)
13
- end
14
-
15
- comments '/(\d+)/(\d+)/(.*)/comments' do |year, month, name|
16
- post = find_post(year.to_i, month.to_i, name) or raise "post not found #{request.path_info}"
17
-
18
- if params['preview']
19
- comments = comments_for(post).push(Shinmun::Comment.new(params))
20
- render 'post.rhtml', :post => post, :comments => comments
21
- else
22
- create_comment(post, params)
23
- redirect post_path(post)
24
- end
10
+ render 'category.rhtml', :name => "Tag: #{tag}", :posts => posts_by_tag[tag]
25
11
  end
26
12
 
27
13
  post '/(\d+)/(\d+)/(.*)' do |year, month, name|
28
- if post = find_post(year.to_i, month.to_i, name)
29
- render 'post.rhtml', :post => post, :comments => comments_for(post)
14
+ if post = posts_by_date[year.to_i][month.to_i][name]
15
+ render 'post.rhtml', :post => post
30
16
  else
31
17
  render '404.rhtml', :path => request.path_info
32
18
  end
33
19
  end
34
20
 
35
21
  archive '/(\d+)/(\d+)' do |year, month|
36
- render 'archive.rhtml', :year => year.to_i, :month => month.to_i, :posts => posts_for_month(year.to_i, month.to_i)
22
+ render('archive.rhtml',
23
+ :year => year.to_i,
24
+ :month => month.to_i,
25
+ :posts => posts_by_date[year.to_i][month.to_i].values)
37
26
  end
38
27
 
39
28
  feed '/index\.rss' do
@@ -41,18 +30,18 @@ Shinmun::Blog.map do
41
30
  end
42
31
 
43
32
  index '/$' do
44
- render 'index.rhtml'
33
+ render 'index.rhtml', :posts => posts[0, 20]
45
34
  end
46
35
 
47
36
  page '/(.*)' do |path|
48
37
  path = path.gsub('..', '')
49
- page = find_page(path)
50
-
51
- if page
52
- render 'page.rhtml', :page => page
53
-
54
- elsif file = store[path]
55
- text file
38
+
39
+ if page = pages[path]
40
+ render 'page.rhtml', :page => page
41
+ elsif File.exist?("public/#{path}")
42
+ file = Rack::File.new(nil)
43
+ file.path = "public/#{path}"
44
+ response.body = file
56
45
  else
57
46
  render '404.rhtml', :path => path
58
47
  end
data/lib/shinmun.rb CHANGED
@@ -1,16 +1,16 @@
1
+ $:.unshift '../../kontrol/lib'
2
+
1
3
  require 'rubygems'
2
4
  require 'fileutils'
3
5
  require 'bluecloth'
4
6
  require 'rubypants'
5
7
  require 'coderay'
6
8
  require 'kontrol'
7
- require 'git_store'
8
9
 
9
10
  begin; require 'redcloth'; rescue LoadError; end
10
11
 
11
12
  require 'shinmun/bluecloth_coderay'
12
13
  require 'shinmun/helpers'
13
- require 'shinmun/handlers'
14
14
  require 'shinmun/blog'
15
15
  require 'shinmun/routes'
16
16
  require 'shinmun/post'
File without changes
File without changes
@@ -1,7 +1,7 @@
1
1
  <h1><%= "#{Date::MONTHNAMES[@month]} #{@year}" %></h1>
2
2
 
3
3
  <div class="articles">
4
- <% for post in @blog.posts_for_month(@year, @month) %>
4
+ <% for post in @posts %>
5
5
  <div class="article">
6
6
  <div class="date"><%= human_date post.date %></div>
7
7
  <h2><%= post_link post %></h2>
@@ -1,4 +1,4 @@
1
- <h1><%= @name %></h1>
1
+ <h1><%= @category %></h1>
2
2
 
3
3
  <div class="articles">
4
4
  <% for post in @posts %>
@@ -1,7 +1,7 @@
1
1
  <h1>Home</h1>
2
2
 
3
3
  <div class="articles">
4
- <% for post in @blog.recent_posts %>
4
+ <% for post in @posts %>
5
5
  <div class="article">
6
6
  <div class="date"><%= human_date post.date %></div>
7
7
  <h2><%= post_link post %></h2>
data/templates/post.rhtml CHANGED
@@ -17,17 +17,5 @@
17
17
  Tagged with <%= @post.tag_list.map {|tag| link_to tag, "/tags/#{tag}" }.join(', ') %>.
18
18
  <% end %>
19
19
  </p>
20
-
21
- <% if @comments %>
22
- <h2>Comments</h2>
23
-
24
- <div class="comments">
25
- <%= render '_comments.rhtml', :comments => @comments %>
26
- </div>
27
- <% end %>
28
-
29
- <h2>Leave a comment</h2>
30
-
31
- <%= render '_comment_form.rhtml', :post => @post %>
32
-
20
+
33
21
  </div>
data/test/blog_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ $:.unshift "#{File.dirname __FILE__}/../lib"
2
+
1
3
  require 'shinmun'
2
4
  require 'rack/mock'
3
5
  require 'rexml/document'
@@ -27,14 +29,19 @@ describe Shinmun::Blog do
27
29
  :categories => ['Ruby', 'Javascript']
28
30
  }
29
31
 
30
- @posts = [blog.create_post(:title => 'New post', :date => '2008-10-10', :category => 'Ruby', :body => 'Body1'),
31
- blog.create_post(:title => 'And this', :date => '2008-10-11', :category => 'Ruby', :body => 'Body2'),
32
- blog.create_post(:title => 'Again', :date => '2008-11-10', :category => 'Javascript', :body => 'Body3')]
32
+ @posts = [Shinmun::Post.new(:title => 'New post', :date => Date.new(2008,10,10), :category => 'Ruby', :body => 'Body1'),
33
+ Shinmun::Post.new(:title => 'And this', :date => Date.new(2008,10,11), :category => 'Ruby', :body => 'Body2'),
34
+ Shinmun::Post.new(:title => 'Again', :date => Date.new(2008,11,10), :category => 'Javascript', :body => 'Body3')]
35
+
36
+ @pages = {
37
+ 'page-1' => Shinmun::Post.new(:title => 'Page 1', :body => 'Body1'),
38
+ 'page-2' => Shinmun::Post.new(:title => 'Page 2', :body => 'Body2')
39
+ }
33
40
 
34
- @pages = [blog.create_page(:title => 'Page 1', :body => 'Body1'),
35
- blog.create_page(:title => 'Page 2', :body => 'Body2')]
41
+ blog.instance_variable_set('@posts', @posts)
42
+ blog.instance_variable_set('@pages', @pages)
36
43
 
37
- blog.store.load
44
+ blog.sort_posts
38
45
  end
39
46
 
40
47
  def request(method, uri, options={})
@@ -73,54 +80,29 @@ describe Shinmun::Blog do
73
80
  end
74
81
 
75
82
  it "should find posts for a category" do
76
- category = blog.find_category('ruby')
77
- category[:name].should == 'Ruby'
83
+ blog.find_category('ruby').should == 'Ruby'
78
84
 
79
- category[:posts].should include(@posts[0])
80
- category[:posts].should include(@posts[1])
81
-
82
- category = blog.find_category('javascript')
83
- category[:name].should == 'Javascript'
84
- category[:posts].should include(@posts[2])
85
- end
86
-
87
- it "should create a post" do
88
- post = blog.create_post(:title => 'New post', :date => '2008-10-10')
89
- blog.store.load
85
+ blog.posts_by_category['Ruby'].should include(@posts[0])
86
+ blog.posts_by_category['Ruby'].should include(@posts[1])
90
87
 
91
- post = blog.find_post(2008, 10, 'new-post')
92
- post.should_not be_nil
93
- post.title.should == 'New post'
94
- post.date.should == Date.new(2008, 10, 10)
95
- post.name.should == 'new-post'
88
+ blog.find_category('javascript').should == 'Javascript'
89
+ blog.posts_by_category['Javascript'].should include(@posts[2])
96
90
  end
97
91
 
98
92
  it "should render posts" do
99
93
  xml = get('/2008/10/new-post').body
100
-
94
+
101
95
  xpath(xml, "//h1")[0].text.should == 'New post'
102
96
  xpath(xml, "//p")[0].text.should == 'Body1'
103
97
  end
104
98
 
105
- it "should render categories" do
106
- get('/categories/ruby.rss')['Content-Type'].should == 'application/rss+xml'
107
-
108
- xml = get('/categories/ruby.rss').body
109
-
110
- xpath(xml, '/rss/channel/title')[0].text.should == 'Ruby'
111
- xpath(xml, '/rss/channel/item/title')[0].text.should == 'And this'
112
- xpath(xml, '/rss/channel/item/pubDate')[0].text.should == "Sat, 11 Oct 2008 00:00:00 +0000"
113
- xpath(xml, '/rss/channel/item/link')[0].text.should == "http://example.org/2008/10/and-this"
114
- xpath(xml, '/rss/channel/item/title')[1].text.should == 'New post'
115
- xpath(xml, '/rss/channel/item/pubDate')[1].text.should == "Fri, 10 Oct 2008 00:00:00 +0000"
116
- xpath(xml, '/rss/channel/item/link')[1].text.should == "http://example.org/2008/10/new-post"
117
-
99
+ it "should render categories" do
118
100
  assert_listing(get('/categories/ruby').body, [['And this', 'Body2'], ['New post', 'Body1']])
119
101
  end
120
102
 
121
103
  it "should render index and archives" do
122
- blog.posts_for_month(2008, 10).should_not be_empty
123
- blog.posts_for_month(2008, 11).should_not be_empty
104
+ blog.posts_by_date[2008][10].should_not be_empty
105
+ blog.posts_by_date[2008][11].should_not be_empty
124
106
 
125
107
  assert_listing(get('/2008/10').body, [['And this', 'Body2'], ['New post', 'Body1']])
126
108
  assert_listing(get('/').body, [['Again', 'Body3'], ['And this', 'Body2'], ['New post', 'Body1']])
@@ -136,21 +118,11 @@ describe Shinmun::Blog do
136
118
  xpath(xml, "//p")[0].text.should == 'Body2'
137
119
  end
138
120
 
139
- it "should post a comment" do
140
- post "/2008/10/new-post/comments?name=Hans&text=Hallo"
141
- post "/2008/10/new-post/comments?name=Peter&text=Servus"
142
-
143
- blog.store.load
144
-
145
- comments = blog.comments_for(@posts[0])
146
-
147
- comments[0].should_not be_nil
148
- comments[0].name.should == 'Hans'
149
- comments[0].text.should == 'Hallo'
121
+ it "should render a post" do
122
+ xml = get('/2008/10/new-post').body
150
123
 
151
- comments[1].should_not be_nil
152
- comments[1].name.should == 'Peter'
153
- comments[1].text.should == 'Servus'
124
+ xpath(xml, "//h1")[0].text.should == 'New post'
125
+ xpath(xml, "//div[@class='date']")[0].text.strip.should == 'October 10, 2008'
154
126
  end
155
127
 
156
128
  end
data/test/post_spec.rb CHANGED
@@ -1,7 +1,15 @@
1
+ $:.unshift '../lib'
2
+
1
3
  require 'shinmun'
2
4
 
3
5
  describe Shinmun::Post do
4
6
 
7
+ BODY = "Patroon is a template engine written in Javascript in about 100 lines
8
+ of code. It takes existing DOM nodes annotated with CSS classes and
9
+ expand a data object according to simple rules. Additionally you may
10
+ use traditional string interpolation inside attribute values and text
11
+ nodes."
12
+
5
13
  POST = <<-END
6
14
  ---
7
15
  category: Javascript
@@ -9,24 +17,79 @@ date: 2008-09-09
9
17
  tags: template, engine, json
10
18
  title: Patroon - a Javascript Template Engine
11
19
  ---
12
- Patroon is a template engine written in Javascript in about 100 lines
13
- of code. It takes existing DOM nodes annotated with CSS classes and
14
- expand a data object according to simple rules. Additionally you may
15
- use traditional string interpolation inside attribute values and text
16
- nodes.
20
+ #{BODY}
17
21
  END
18
22
 
19
- it 'should parse and dump in the same way' do
20
- Shinmun::Post.new(:type => 'md', :src => POST).dump.should == (POST)
23
+ it "should infer the name from title" do
24
+ post = Shinmun::Post.new(:title => 'Patroon - a Javascript Template Engine')
25
+ post.name.should == 'patroon-a-javascript-template-engine'
26
+ end
27
+
28
+ it "should return tags as list" do
29
+ post = Shinmun::Post.new
30
+ post.tags = 'a,b,c,d'
31
+ post.tag_list.should == ['a', 'b', 'c', 'd']
21
32
  end
22
33
 
34
+ it "should infer path from date and name for posts" do
35
+ post = Shinmun::Post.new
36
+ post.name = 'post'
37
+ post.date = Date.new(2010,1,1)
38
+ post.path.should == 'posts/2010/1/post.md'
39
+ end
40
+
41
+ it "should infer path from name for pages" do
42
+ post = Shinmun::Post.new
43
+ post.name = 'page'
44
+ post.path.should == 'pages/page.md'
45
+ end
46
+
47
+ it "should set type, name and mtime upon loading a post" do
48
+ File.stub!(:mtime).and_return(Time.local(2010, 1, 1))
49
+ File.stub!(:read).and_return(POST)
50
+
51
+ post = Shinmun::Post.new(:file => 'posts/2010/01/a-post.md')
52
+ post.load
53
+ post.type.should == 'md'
54
+ post.name.should == 'a-post'
55
+ post.mtime.should == Time.local(2010, 1, 1)
56
+ end
57
+
58
+ it "should detect a changed file" do
59
+ File.should_receive(:mtime).with('file').and_return(Time.local(2009, 1, 1))
60
+ File.should_receive(:mtime).with('file').and_return(Time.local(2010, 1, 1))
61
+
62
+ post = Shinmun::Post.new
63
+ post.file = 'file'
64
+ post.mtime = Time.local(2009, 1, 1)
65
+ post.changed?.should be_false
66
+ post.changed?.should be_true
67
+ end
68
+
23
69
  it "should parse the yaml header" do
24
- post = Shinmun::Post.new(:type => 'md', :src => POST)
70
+ post = Shinmun::Post.new(:type => 'md')
71
+ post.parse(POST)
25
72
  post.title.should == 'Patroon - a Javascript Template Engine'
26
73
  post.category.should == 'Javascript'
27
74
  post.date.should == Date.new(2008,9,9)
28
75
  post.tags.should == 'template, engine, json'
29
- post.tag_list.should == ['template', 'engine', 'json']
76
+ post.body.chop == BODY
77
+ end
78
+
79
+ it "should not parse the yaml header if not present" do
80
+ post = Shinmun::Post.new
81
+ post.parse('just the body')
82
+ post.body.should == 'just the body'
83
+ end
84
+
85
+ it "should transform the body" do
86
+ post = Shinmun::Post.new(:title => 'test', :body => '**bold**')
87
+ post.body_html.should == '<p><strong>bold</strong></p>'
88
+ end
89
+
90
+ it "should transform according to type" do
91
+ post = Shinmun::Post.new(:title => 'test', :type => 'html', :body => '**bold**')
92
+ post.body_html.should_not == '<p><strong>bold</strong></p>'
30
93
  end
31
94
 
32
95
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shinmun
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: "0.9"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Georgi
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-07 00:00:00 +01:00
12
+ date: 2010-02-08 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: "0"
43
+ version: "1.0"
44
44
  version:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: coderay
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: "0"
53
+ version: 0.9.1
54
54
  version:
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: kontrol
@@ -60,17 +60,7 @@ dependencies:
60
60
  requirements:
61
61
  - - ">="
62
62
  - !ruby/object:Gem::Version
63
- version: "0"
64
- version:
65
- - !ruby/object:Gem::Dependency
66
- name: git_store
67
- type: :runtime
68
- version_requirement:
69
- version_requirements: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - ">="
72
- - !ruby/object:Gem::Version
73
- version: "0"
63
+ version: 0.3.1
74
64
  version:
75
65
  description: git-based blog engine.
76
66
  email: matti.georgi@gmail.com
@@ -81,29 +71,27 @@ extensions: []
81
71
  extra_rdoc_files:
82
72
  - README.md
83
73
  files:
74
+ - .gems
84
75
  - README.md
76
+ - LICENSE
85
77
  - Rakefile
86
- - assets/print.css
87
- - assets/styles.css
78
+ - public/stylesheets/print.css
79
+ - public/stylesheets/styles.css
88
80
  - bin/shinmun
89
81
  - config.ru
90
82
  - lib/shinmun.rb
91
83
  - lib/shinmun/blog.rb
92
84
  - lib/shinmun/bluecloth_coderay.rb
93
85
  - lib/shinmun/comment.rb
94
- - lib/shinmun/handlers.rb
95
86
  - lib/shinmun/helpers.rb
96
87
  - lib/shinmun/post.rb
97
88
  - lib/shinmun/routes.rb
98
89
  - templates/index.rhtml
99
90
  - templates/page.rhtml
100
91
  - templates/404.rhtml
101
- - templates/_comments.rhtml
102
92
  - templates/category.rhtml
103
- - templates/_comment_form.rhtml
104
93
  - templates/post.rhtml
105
94
  - templates/index.rxml
106
- - templates/category.rxml
107
95
  - templates/archive.rhtml
108
96
  - templates/layout.rhtml
109
97
  - test/blog_spec.rb
@@ -1,19 +0,0 @@
1
- module Shinmun
2
-
3
- class ERBHandler
4
- def read(data)
5
- ERB.new(data)
6
- end
7
- end
8
-
9
- class PostHandler
10
- def read(data)
11
- Post.new(:src => data)
12
- end
13
-
14
- def write(post)
15
- post.dump
16
- end
17
- end
18
-
19
- end
@@ -1,21 +0,0 @@
1
- <div class="comment-form">
2
- <form action="<%= post_path @post %>/comments" method="POST">
3
- <p>
4
- <label>Name</label><br/>
5
- <input type="text" name="name" size="40" value="<%= @params['name'] %>"/>
6
- </p>
7
- <p>
8
- <label>Website</label><br/>
9
- <input type="text" name="website" size="40" value="<%= @params['website'] %>"/>
10
- </p>
11
- <p>
12
- <label>Comment</label><br/>
13
- <textarea name="text" cols="60" rows="10"><%= @params['text'] %></textarea>
14
- </p>
15
- <p>
16
- <input type="submit" value="Post comment"/>
17
- <input type="checkbox" name="preview" value="1"/>
18
- Preview
19
- </p>
20
- </form>
21
- </div>
@@ -1,11 +0,0 @@
1
- <% for comment in @comments %>
2
- <div class="comment">
3
- <div class="top">
4
- <%= comment.website.to_s.empty? ? comment.name : link_to(comment.name, comment.website) %> said
5
- <%= distance_of_time_in_words(comment.time, Time.now, true) %> ago:
6
- </div>
7
- <div class="body">
8
- <%= markdown(comment.text, :filter_html, :filter_styles) %>
9
- </div>
10
- </div>
11
- <% end %>
@@ -1,20 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <rss version="2.0">
3
- <channel>
4
- <title><%= @name %></title>
5
- <link><%= @blog.url %>/categories/<%= urlify @name %></link>
6
- <language><%= @blog.language %></language>
7
- <copyright><%= @blog.author %></copyright>
8
- <pubDate><%= rfc822 Time.now %></pubDate>
9
- <% for post in @posts[0, 20] %>
10
- <item>
11
- <title><%= post.title %></title>
12
- <category><%= post.category %></category>
13
- <description><%= strip_tags post.summary %></description>
14
- <author><%= @author || @blog.author %></author>
15
- <link><%= @blog.url %><%= post_path post %></link>
16
- <pubDate><%= rfc822 post.date %></pubDate>
17
- </item>
18
- <% end %>
19
- </channel>
20
- </rss>