shinmun 0.5.2 → 0.9

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