shinmun 0.2 → 0.5
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/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
|