shinmun 0.2 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +145 -210
- data/Rakefile +21 -53
- data/assets/print.css +76 -0
- data/assets/styles.css +91 -0
- data/bin/shinmun +24 -7
- data/config.ru +16 -0
- data/lib/shinmun/blog.rb +113 -251
- data/lib/shinmun/bluecloth_coderay.rb +21 -0
- data/lib/shinmun/comment.rb +3 -30
- data/lib/shinmun/handlers.rb +19 -0
- data/lib/shinmun/helpers.rb +46 -80
- data/lib/shinmun/post.rb +67 -98
- data/lib/shinmun/routes.rb +61 -0
- data/lib/shinmun.rb +8 -14
- data/templates/404.rhtml +4 -0
- data/templates/_comment_form.rhtml +21 -0
- data/templates/_comments.rhtml +11 -0
- data/templates/archive.rhtml +11 -0
- data/templates/category.rhtml +11 -0
- data/templates/category.rxml +20 -0
- data/templates/index.rhtml +11 -0
- data/templates/index.rxml +21 -0
- data/templates/layout.rhtml +44 -0
- data/templates/page.rhtml +5 -0
- data/templates/post.rhtml +33 -0
- data/test/blog_spec.rb +156 -0
- data/test/post_spec.rb +32 -0
- metadata +51 -16
- data/.gitignore +0 -3
- data/LICENSE +0 -18
- data/lib/shinmun/admin_controller.rb +0 -161
- data/lib/shinmun/aggregations/delicious.rb +0 -57
- data/lib/shinmun/aggregations/flickr.rb +0 -81
- data/lib/shinmun/cache.rb +0 -59
- data/lib/shinmun/controller.rb +0 -135
- data/lib/shinmun/template.rb +0 -39
data/test/blog_spec.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'shinmun'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'rexml/xpath'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
describe Shinmun::Blog do
|
8
|
+
|
9
|
+
DIR = '/tmp/shinmun-test'
|
10
|
+
|
11
|
+
attr_reader :blog
|
12
|
+
|
13
|
+
before do
|
14
|
+
ENV['RACK_ENV'] = 'production'
|
15
|
+
|
16
|
+
FileUtils.rm_rf DIR
|
17
|
+
|
18
|
+
Shinmun::Blog.init(DIR)
|
19
|
+
|
20
|
+
@blog = Shinmun::Blog.new(DIR)
|
21
|
+
|
22
|
+
blog.config = {
|
23
|
+
:title => 'Title',
|
24
|
+
:description => 'Description',
|
25
|
+
:language => 'en',
|
26
|
+
:author => 'The Author',
|
27
|
+
:categories => ['Ruby', 'Javascript']
|
28
|
+
}
|
29
|
+
|
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')]
|
33
|
+
|
34
|
+
@pages = [blog.create_page(:title => 'Page 1', :body => 'Body1'),
|
35
|
+
blog.create_page(:title => 'Page 2', :body => 'Body2')]
|
36
|
+
|
37
|
+
blog.store.load
|
38
|
+
end
|
39
|
+
|
40
|
+
def request(method, uri, options={})
|
41
|
+
@request = Rack::MockRequest.new(blog)
|
42
|
+
@response = @request.request(method, uri, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def get(*args)
|
46
|
+
request(:get, *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def post(*args)
|
50
|
+
request(:post, *args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def xpath(xml, path)
|
54
|
+
REXML::XPath.match(REXML::Document.new(xml), path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def query(hash)
|
58
|
+
Rack::Utils.build_query(hash)
|
59
|
+
end
|
60
|
+
|
61
|
+
def assert_listing(xml, list)
|
62
|
+
titles = xpath(xml, "//h2/a")
|
63
|
+
summaries = xpath(xml, "//p")
|
64
|
+
|
65
|
+
list.each_with_index do |(title, summary), i|
|
66
|
+
titles[i].text.should == title
|
67
|
+
summaries[i].text.to_s.strip.should == summary
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should load templates" do
|
72
|
+
blog.load_template("index.rhtml").should be_kind_of(ERB)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should find posts for a category" do
|
76
|
+
category = blog.find_category('ruby')
|
77
|
+
category[:name].should == 'Ruby'
|
78
|
+
|
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
|
90
|
+
|
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'
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should render posts" do
|
99
|
+
xml = get('/2008/10/new-post').body
|
100
|
+
|
101
|
+
xpath(xml, "//h1")[0].text.should == 'New post'
|
102
|
+
xpath(xml, "//p")[0].text.should == 'Body1'
|
103
|
+
end
|
104
|
+
|
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
|
+
|
118
|
+
assert_listing(get('/categories/ruby').body, [['And this', 'Body2'], ['New post', 'Body1']])
|
119
|
+
end
|
120
|
+
|
121
|
+
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
|
124
|
+
|
125
|
+
assert_listing(get('/2008/10').body, [['And this', 'Body2'], ['New post', 'Body1']])
|
126
|
+
assert_listing(get('/').body, [['Again', 'Body3'], ['And this', 'Body2'], ['New post', 'Body1']])
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should render pages" do
|
130
|
+
xml = get('/page-1').body
|
131
|
+
xpath(xml, "//h1")[0].text.should == 'Page 1'
|
132
|
+
xpath(xml, "//p")[0].text.should == 'Body1'
|
133
|
+
|
134
|
+
xml = get('/page-2').body
|
135
|
+
xpath(xml, "//h1")[0].text.should == 'Page 2'
|
136
|
+
xpath(xml, "//p")[0].text.should == 'Body2'
|
137
|
+
end
|
138
|
+
|
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'
|
150
|
+
|
151
|
+
comments[1].should_not be_nil
|
152
|
+
comments[1].name.should == 'Peter'
|
153
|
+
comments[1].text.should == 'Servus'
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
data/test/post_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'shinmun'
|
2
|
+
|
3
|
+
describe Shinmun::Post do
|
4
|
+
|
5
|
+
POST = <<-END
|
6
|
+
---
|
7
|
+
category: Javascript
|
8
|
+
date: 2008-09-09
|
9
|
+
tags: template, engine, json
|
10
|
+
title: Patroon - a Javascript Template Engine
|
11
|
+
---
|
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.
|
17
|
+
END
|
18
|
+
|
19
|
+
it 'should parse and dump in the same way' do
|
20
|
+
Shinmun::Post.new(:type => 'md', :src => POST).dump.should == (POST)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should parse the yaml header" do
|
24
|
+
post = Shinmun::Post.new(:type => 'md', :src => POST)
|
25
|
+
post.title.should == 'Patroon - a Javascript Template Engine'
|
26
|
+
post.category.should == 'Javascript'
|
27
|
+
post.date.should == Date.new(2008,9,9)
|
28
|
+
post.tags.should == 'template, engine, json'
|
29
|
+
post.tag_list.should == ['template', 'engine', 'json']
|
30
|
+
end
|
31
|
+
|
32
|
+
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.
|
4
|
+
version: "0.5"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Georgi
|
@@ -9,11 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-02-07 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: BlueCloth
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
@@ -23,6 +24,7 @@ dependencies:
|
|
23
24
|
version:
|
24
25
|
- !ruby/object:Gem::Dependency
|
25
26
|
name: rubypants
|
27
|
+
type: :runtime
|
26
28
|
version_requirement:
|
27
29
|
version_requirements: !ruby/object:Gem::Requirement
|
28
30
|
requirements:
|
@@ -30,7 +32,27 @@ dependencies:
|
|
30
32
|
- !ruby/object:Gem::Version
|
31
33
|
version: "0"
|
32
34
|
version:
|
33
|
-
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rack
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: coderay
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
description: git-based blog engine.
|
34
56
|
email: matti.georgi@gmail.com
|
35
57
|
executables:
|
36
58
|
- shinmun
|
@@ -39,24 +61,37 @@ extensions: []
|
|
39
61
|
extra_rdoc_files:
|
40
62
|
- README.md
|
41
63
|
files:
|
42
|
-
- .gitignore
|
43
|
-
- LICENSE
|
44
64
|
- README.md
|
45
65
|
- Rakefile
|
66
|
+
- assets/print.css
|
67
|
+
- assets/styles.css
|
46
68
|
- bin/shinmun
|
69
|
+
- config.ru
|
47
70
|
- lib/shinmun.rb
|
48
|
-
- lib/shinmun/admin_controller.rb
|
49
|
-
- lib/shinmun/aggregations/delicious.rb
|
50
|
-
- lib/shinmun/aggregations/flickr.rb
|
51
71
|
- lib/shinmun/blog.rb
|
52
|
-
- lib/shinmun/
|
72
|
+
- lib/shinmun/bluecloth_coderay.rb
|
53
73
|
- lib/shinmun/comment.rb
|
54
|
-
- lib/shinmun/
|
74
|
+
- lib/shinmun/handlers.rb
|
55
75
|
- lib/shinmun/helpers.rb
|
56
76
|
- lib/shinmun/post.rb
|
57
|
-
- lib/shinmun/
|
77
|
+
- lib/shinmun/routes.rb
|
78
|
+
- templates/index.rhtml
|
79
|
+
- templates/page.rhtml
|
80
|
+
- templates/404.rhtml
|
81
|
+
- templates/_comments.rhtml
|
82
|
+
- templates/category.rhtml
|
83
|
+
- templates/_comment_form.rhtml
|
84
|
+
- templates/post.rhtml
|
85
|
+
- templates/index.rxml
|
86
|
+
- templates/category.rxml
|
87
|
+
- templates/archive.rhtml
|
88
|
+
- templates/layout.rhtml
|
89
|
+
- test/blog_spec.rb
|
90
|
+
- test/post_spec.rb
|
58
91
|
has_rdoc: true
|
59
|
-
homepage: http://shinmun
|
92
|
+
homepage: http://github.com/georgi/shinmun
|
93
|
+
licenses: []
|
94
|
+
|
60
95
|
post_install_message:
|
61
96
|
rdoc_options: []
|
62
97
|
|
@@ -76,10 +111,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
111
|
version:
|
77
112
|
requirements: []
|
78
113
|
|
79
|
-
rubyforge_project:
|
80
|
-
rubygems_version: 1.
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 1.3.5
|
81
116
|
signing_key:
|
82
|
-
specification_version:
|
83
|
-
summary:
|
117
|
+
specification_version: 3
|
118
|
+
summary: git-based blog engine
|
84
119
|
test_files: []
|
85
120
|
|
data/.gitignore
DELETED
data/LICENSE
DELETED
@@ -1,18 +0,0 @@
|
|
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.
|
@@ -1,161 +0,0 @@
|
|
1
|
-
require 'rack'
|
2
|
-
require 'json'
|
3
|
-
|
4
|
-
module Shinmun
|
5
|
-
|
6
|
-
class AdminController
|
7
|
-
|
8
|
-
def initialize(blog)
|
9
|
-
@blog = blog
|
10
|
-
end
|
11
|
-
|
12
|
-
def tree(request)
|
13
|
-
file_node(request.path_info[1..-1], 1).to_json
|
14
|
-
end
|
15
|
-
|
16
|
-
def get_page(request, path)
|
17
|
-
page = @blog.find_page(path)
|
18
|
-
|
19
|
-
{ :title => page.title,
|
20
|
-
:date => page.date,
|
21
|
-
:category => page.category,
|
22
|
-
:tags => page.tags ? page.tags.join(',') : nil,
|
23
|
-
:body => page.body }.to_json
|
24
|
-
end
|
25
|
-
|
26
|
-
def put_page(request, path)
|
27
|
-
params = request.params
|
28
|
-
page = @blog.find_page(path)
|
29
|
-
|
30
|
-
page.title = params['title']
|
31
|
-
page.author = params['author']
|
32
|
-
page.date = Date.parse(params['date']) rescue nil
|
33
|
-
page.category = params['category']
|
34
|
-
page.tags = params['tags']
|
35
|
-
page.languages = params['languages']
|
36
|
-
page.body = params['body']
|
37
|
-
page.save
|
38
|
-
|
39
|
-
git_add(page.filename, 'changed')
|
40
|
-
|
41
|
-
return ''
|
42
|
-
end
|
43
|
-
|
44
|
-
def get_file(request, path)
|
45
|
-
File.read(path)
|
46
|
-
end
|
47
|
-
|
48
|
-
def put_file(request, path)
|
49
|
-
File.open(path, 'w') do |io|
|
50
|
-
io << request.params['body']
|
51
|
-
end
|
52
|
-
git_add(path, 'changed')
|
53
|
-
return ''
|
54
|
-
end
|
55
|
-
|
56
|
-
def data(request)
|
57
|
-
path = request.path_info[1..-1]
|
58
|
-
match = path.match(/^posts\/(.*)\.(.*)$/)
|
59
|
-
method = request.request_method.downcase
|
60
|
-
|
61
|
-
if match
|
62
|
-
send("#{method}_page", request, match[1])
|
63
|
-
else
|
64
|
-
send("#{method}_file", request, path)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def new_folder(request)
|
69
|
-
path = request.path_info[1..-1] + '/' + request.params['name']
|
70
|
-
|
71
|
-
unless File.exist?(path)
|
72
|
-
Dir.mkdir(path)
|
73
|
-
end
|
74
|
-
|
75
|
-
return ''
|
76
|
-
end
|
77
|
-
|
78
|
-
def new_file(request)
|
79
|
-
path = request.path_info[1..-1] + '/' + request.params['name']
|
80
|
-
|
81
|
-
unless File.exist?(path)
|
82
|
-
File.open(path, "w").close
|
83
|
-
git_add(path, 'created')
|
84
|
-
end
|
85
|
-
|
86
|
-
return ''
|
87
|
-
end
|
88
|
-
|
89
|
-
def rename(request)
|
90
|
-
path = request.path_info[1..-1]
|
91
|
-
dest = File.basename(path) + '/' + request.params['name']
|
92
|
-
|
93
|
-
if File.exist?(path) and !File.exist?(dest)
|
94
|
-
`git mv #{path} #{dest}`
|
95
|
-
`git commit -m 'moved #{path} to #{dest}'`
|
96
|
-
end
|
97
|
-
|
98
|
-
return ''
|
99
|
-
end
|
100
|
-
|
101
|
-
def delete(request)
|
102
|
-
path = request.path_info[1..-1]
|
103
|
-
|
104
|
-
if File.file?(path)
|
105
|
-
`git rm #{path}`
|
106
|
-
`git commit -m 'deleted #{path}'`
|
107
|
-
end
|
108
|
-
|
109
|
-
return ''
|
110
|
-
end
|
111
|
-
|
112
|
-
def call(env)
|
113
|
-
request = Rack::Request.new(env)
|
114
|
-
response = Rack::Response.new
|
115
|
-
action = request.params['action']
|
116
|
-
|
117
|
-
response.body = send(action, request) if self.class.public_instance_methods.include?(action)
|
118
|
-
|
119
|
-
response.status = 200
|
120
|
-
response.finish
|
121
|
-
end
|
122
|
-
|
123
|
-
protected
|
124
|
-
|
125
|
-
def git_add(file, message)
|
126
|
-
`git add #{file}`
|
127
|
-
`git commit -m '#{message} #{file}'`
|
128
|
-
end
|
129
|
-
|
130
|
-
def entries_for(path)
|
131
|
-
Dir.entries(path).reject { |f| f.match /(\.|~)$/ }.sort
|
132
|
-
end
|
133
|
-
|
134
|
-
def root
|
135
|
-
{ :children => ['config', 'posts', 'public', 'templates'].map { |f| file_node(f, 1) } }
|
136
|
-
end
|
137
|
-
|
138
|
-
def file_node(path, depth)
|
139
|
-
return root if path.empty?
|
140
|
-
|
141
|
-
stat = File.stat(path)
|
142
|
-
|
143
|
-
hash = {
|
144
|
-
:id => path,
|
145
|
-
:cls => stat.file? ? 'file' : 'folder',
|
146
|
-
:text => File.basename(path),
|
147
|
-
:leaf => stat.file?
|
148
|
-
}
|
149
|
-
|
150
|
-
unless stat.file?
|
151
|
-
hash[:children] = entries_for(path).map do |entry|
|
152
|
-
file_node(File.join(path, entry), depth - 1)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
hash
|
157
|
-
end
|
158
|
-
|
159
|
-
end
|
160
|
-
|
161
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
require 'open-uri'
|
2
|
-
require 'time'
|
3
|
-
require 'rexml/document'
|
4
|
-
|
5
|
-
class Delicious
|
6
|
-
include REXML
|
7
|
-
|
8
|
-
attr_accessor :url, :items, :link, :title, :days
|
9
|
-
|
10
|
-
# This object holds given information of an item
|
11
|
-
class DeliciousItem < Struct.new(:link, :title, :description, :description_link, :date)
|
12
|
-
def to_s; title end
|
13
|
-
end
|
14
|
-
|
15
|
-
# Pass the url to the RSS feed you would like to keep tabs on
|
16
|
-
# by default this will request the rss from the server right away and
|
17
|
-
# fill the items array
|
18
|
-
def initialize(url, refresh = true)
|
19
|
-
self.items = []
|
20
|
-
self.url = url
|
21
|
-
self.days = {}
|
22
|
-
self.refresh if refresh
|
23
|
-
end
|
24
|
-
|
25
|
-
# This method lets you refresh the items in the items array
|
26
|
-
# useful if you keep the object cached in memory and
|
27
|
-
def refresh
|
28
|
-
open(@url) do |http|
|
29
|
-
parse(http.read)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def parse(body)
|
36
|
-
|
37
|
-
xml = Document.new(body)
|
38
|
-
|
39
|
-
self.items = []
|
40
|
-
self.link = XPath.match(xml, "//channel/link/text()").first.value rescue ""
|
41
|
-
self.title = XPath.match(xml, "//channel/title/text()").first.value rescue ""
|
42
|
-
|
43
|
-
XPath.each(xml, "//item/") do |elem|
|
44
|
-
item = DeliciousItem.new
|
45
|
-
item.title = XPath.match(elem, "title/text()").first.value rescue ""
|
46
|
-
item.link = XPath.match(elem, "link/text()").first.value rescue ""
|
47
|
-
item.description = XPath.match(elem, "description/text()").first.value rescue ""
|
48
|
-
item.date = Time.mktime(*ParseDate.parsedate(XPath.match(elem, "dc:date/text()").first.value)) rescue Time.now
|
49
|
-
|
50
|
-
item.description_link = item.description
|
51
|
-
item.description.gsub!(/<\/?a\b.*?>/, "") # remove all <a> tags
|
52
|
-
items << item
|
53
|
-
end
|
54
|
-
|
55
|
-
self.items = items.sort_by { |item| item.date }.reverse
|
56
|
-
end
|
57
|
-
end
|
@@ -1,81 +0,0 @@
|
|
1
|
-
require 'open-uri'
|
2
|
-
require 'time'
|
3
|
-
require 'rexml/document'
|
4
|
-
|
5
|
-
# Example:
|
6
|
-
#
|
7
|
-
# flickr = Flickr.new('http://www.flickr.com/services/feeds/photos_public.gne?id=40235412@N00&format=rss_200')
|
8
|
-
# flickr.pics.each do |pic|
|
9
|
-
# puts "#{pic.title} @ #{pic.link} updated at #{pic.date}"
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
class FlickrAggregation
|
13
|
-
include REXML
|
14
|
-
|
15
|
-
def choose(num)
|
16
|
-
return pics unless pics.size > num
|
17
|
-
bag = []
|
18
|
-
set = pics.dup
|
19
|
-
num.times {|x| bag << set.delete_at(rand(set.size))}
|
20
|
-
bag
|
21
|
-
end
|
22
|
-
|
23
|
-
attr_accessor :url, :pics, :link, :title, :description
|
24
|
-
|
25
|
-
# This object holds given information of a picture
|
26
|
-
class Picture
|
27
|
-
attr_accessor :link, :title, :date, :description, :thumbnail
|
28
|
-
|
29
|
-
def to_s
|
30
|
-
title
|
31
|
-
end
|
32
|
-
|
33
|
-
def date=(value)
|
34
|
-
@date = Time.parse(value)
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
# Pass the url to the RSS feed you would like to keep tabs on
|
40
|
-
# by default this will request the rss from the server right away and
|
41
|
-
# fill the tasks array
|
42
|
-
def initialize(url, refresh = true)
|
43
|
-
self.pics = []
|
44
|
-
self.url = url
|
45
|
-
self.refresh if refresh
|
46
|
-
end
|
47
|
-
|
48
|
-
# This method lets you refresh the tasks int the tasks array
|
49
|
-
# useful if you keep the object cached in memory and
|
50
|
-
def refresh
|
51
|
-
open(@url) do |http|
|
52
|
-
parse(http.read)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def parse(body)
|
59
|
-
|
60
|
-
xml = Document.new(body)
|
61
|
-
|
62
|
-
self.pics = []
|
63
|
-
self.link = XPath.match(xml, "//channel/link/text()").to_s
|
64
|
-
self.title = XPath.match(xml, "//channel/title/text()").to_s
|
65
|
-
self.description = XPath.match(xml, "//channel/description/text()").to_s
|
66
|
-
|
67
|
-
XPath.each(xml, "//item/") do |elem|
|
68
|
-
|
69
|
-
picture = Picture.new
|
70
|
-
picture.title = XPath.match(elem, "title/text()").to_s
|
71
|
-
picture.date = XPath.match(elem, "pubDate/text()").to_s
|
72
|
-
picture.link = XPath.match(elem, "link/text()").to_s
|
73
|
-
picture.description = XPath.match(elem, "description/text()").to_s
|
74
|
-
picture.thumbnail = XPath.match(elem, "media:thumbnail/@url").to_s
|
75
|
-
|
76
|
-
pics << picture
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
|
data/lib/shinmun/cache.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
module Shinmun
|
2
|
-
|
3
|
-
# A simple hashtable, which loads only changed files by calling reload.
|
4
|
-
class Cache
|
5
|
-
|
6
|
-
# Call with a block to specify how the data is loaded.
|
7
|
-
# This is the default behaviour: Cache.new {|file| File.read(file) }
|
8
|
-
def initialize(&block)
|
9
|
-
@map = {}
|
10
|
-
@callback = block || proc { |file| File.read(file) }
|
11
|
-
end
|
12
|
-
|
13
|
-
# Load a file into the cache, transform it according to callback
|
14
|
-
# and remember the modification time.
|
15
|
-
def load(file)
|
16
|
-
data = @callback.call(file)
|
17
|
-
@map[file] = [data, File.mtime(file)]
|
18
|
-
data
|
19
|
-
end
|
20
|
-
|
21
|
-
def remove(file)
|
22
|
-
@map.delete(file)
|
23
|
-
end
|
24
|
-
|
25
|
-
def dirty_files
|
26
|
-
@map.map { |file, (data, mtime)| mtime != File.mtime(file) ? file : nil }.compact
|
27
|
-
end
|
28
|
-
|
29
|
-
def reload!
|
30
|
-
@map.keys.each { |file| load file }
|
31
|
-
end
|
32
|
-
|
33
|
-
def reload_dirty!
|
34
|
-
dirty_files.each { |file| load file }
|
35
|
-
end
|
36
|
-
|
37
|
-
# Access the cache by filename.
|
38
|
-
def [](file)
|
39
|
-
data, mtime = @map[file]
|
40
|
-
data or load(file)
|
41
|
-
end
|
42
|
-
|
43
|
-
def values
|
44
|
-
@map.values.map { |data, | data }
|
45
|
-
end
|
46
|
-
|
47
|
-
# Are there any files loaded?
|
48
|
-
def empty?
|
49
|
-
@map.empty?
|
50
|
-
end
|
51
|
-
|
52
|
-
# Is there any file in this cache, which has changed?
|
53
|
-
def dirty?
|
54
|
-
dirty_files.size > 0
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|