shinmun 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +127 -62
  3. data/Rakefile +23 -11
  4. data/bin/shinmun +6 -1
  5. data/lib/shinmun/admin_controller.rb +161 -0
  6. data/lib/shinmun/aggregations/delicious.rb +57 -0
  7. data/lib/shinmun/aggregations/flickr.rb +81 -0
  8. data/lib/shinmun/blog.rb +319 -0
  9. data/lib/shinmun/cache.rb +59 -0
  10. data/lib/shinmun/comment.rb +44 -0
  11. data/lib/shinmun/controller.rb +135 -0
  12. data/lib/shinmun/helpers.rb +116 -0
  13. data/lib/shinmun/post.rb +166 -0
  14. data/lib/shinmun/template.rb +39 -0
  15. data/lib/shinmun.rb +14 -411
  16. metadata +15 -81
  17. data/example/posts/2008/9/example.md +0 -19
  18. data/example/posts/blog.yml +0 -10
  19. data/example/posts/uuid.state +0 -3
  20. data/example/public/controllers/comments.php +0 -56
  21. data/example/public/images/loading.gif +0 -0
  22. data/example/public/javascripts/comments.js +0 -60
  23. data/example/public/javascripts/highlight.js +0 -505
  24. data/example/public/javascripts/images/bg-fill.png +0 -0
  25. data/example/public/javascripts/images/bg.png +0 -0
  26. data/example/public/javascripts/images/blockquote.png +0 -0
  27. data/example/public/javascripts/images/bold.png +0 -0
  28. data/example/public/javascripts/images/code.png +0 -0
  29. data/example/public/javascripts/images/h1.png +0 -0
  30. data/example/public/javascripts/images/hr.png +0 -0
  31. data/example/public/javascripts/images/img.png +0 -0
  32. data/example/public/javascripts/images/italic.png +0 -0
  33. data/example/public/javascripts/images/link.png +0 -0
  34. data/example/public/javascripts/images/ol.png +0 -0
  35. data/example/public/javascripts/images/redo.png +0 -0
  36. data/example/public/javascripts/images/separator.png +0 -0
  37. data/example/public/javascripts/images/ul.png +0 -0
  38. data/example/public/javascripts/images/undo.png +0 -0
  39. data/example/public/javascripts/images/wmd-on.png +0 -0
  40. data/example/public/javascripts/images/wmd.png +0 -0
  41. data/example/public/javascripts/jquery-form.js +0 -869
  42. data/example/public/javascripts/jquery.js +0 -3383
  43. data/example/public/javascripts/languages/1c.js +0 -82
  44. data/example/public/javascripts/languages/axapta.js +0 -52
  45. data/example/public/javascripts/languages/bash.js +0 -80
  46. data/example/public/javascripts/languages/diff.js +0 -64
  47. data/example/public/javascripts/languages/dos.js +0 -33
  48. data/example/public/javascripts/languages/dynamic.js +0 -460
  49. data/example/public/javascripts/languages/ini.js +0 -36
  50. data/example/public/javascripts/languages/javascript.js +0 -38
  51. data/example/public/javascripts/languages/lisp.js +0 -86
  52. data/example/public/javascripts/languages/mel.js +0 -50
  53. data/example/public/javascripts/languages/profile.js +0 -50
  54. data/example/public/javascripts/languages/renderman.js +0 -71
  55. data/example/public/javascripts/languages/smalltalk.js +0 -53
  56. data/example/public/javascripts/languages/sql.js +0 -50
  57. data/example/public/javascripts/languages/static.js +0 -175
  58. data/example/public/javascripts/languages/vbscript.js +0 -25
  59. data/example/public/javascripts/languages/www.js +0 -245
  60. data/example/public/javascripts/prettyDate.js +0 -36
  61. data/example/public/javascripts/showdown.js +0 -421
  62. data/example/public/javascripts/template.js +0 -165
  63. data/example/public/javascripts/wmd-base.js +0 -1799
  64. data/example/public/javascripts/wmd-plus.js +0 -311
  65. data/example/public/javascripts/wmd.js +0 -73
  66. data/example/public/stylesheets/grid.css +0 -243
  67. data/example/public/stylesheets/grid.png +0 -0
  68. data/example/public/stylesheets/highlight/ascetic.css +0 -38
  69. data/example/public/stylesheets/highlight/dark.css +0 -96
  70. data/example/public/stylesheets/highlight/default.css +0 -91
  71. data/example/public/stylesheets/highlight/far.css +0 -95
  72. data/example/public/stylesheets/highlight/idea.css +0 -75
  73. data/example/public/stylesheets/highlight/sunburst.css +0 -112
  74. data/example/public/stylesheets/highlight/zenburn.css +0 -108
  75. data/example/public/stylesheets/print.css +0 -76
  76. data/example/public/stylesheets/reset.css +0 -45
  77. data/example/public/stylesheets/style.css +0 -141
  78. data/example/public/stylesheets/typography.css +0 -59
  79. data/example/templates/feed.rxml +0 -21
  80. data/example/templates/layout.rhtml +0 -54
  81. data/example/templates/page.rhtml +0 -4
  82. data/example/templates/post.rhtml +0 -57
  83. data/example/templates/posts.rhtml +0 -10
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
1
  *~
2
+ doc/*
3
+ pkg/*
data/README.md CHANGED
@@ -1,37 +1,44 @@
1
1
  Shinmun, a small and beautiful blog engine
2
2
  ==========================================
3
3
 
4
- ### Intro
5
-
6
4
  Shinmun is a **minimalist blog engine**. You just write posts as text files,
7
5
  render them to static files and push your blog to your server.
8
6
 
9
7
  This allows you to write posts in your favorite editor like Emacs or
10
8
  VI and use a VCS like git.
11
9
 
12
- Your layout can be customized by set of *ERB templates*. These
10
+ Your layout can be customized by a set of *ERB templates*. These
13
11
  templates have access to `Post` objects and *helper methods* so that
14
12
  anybody who knows *Rails* should feel comfortable with it.
15
13
 
16
- Shinmun has some common features of blog engines like:
17
14
 
18
- * Index summary page
19
- * Category summary page
20
- * Archive pages for each month
15
+ ### Shinmun Features
16
+
17
+ * Index listing
18
+ * Category listing
19
+ * Archive listings for each month
21
20
  * RSS feeds for index and category pages
21
+ * Builtin webserver for realtime rendering
22
+ * Compression of javascript files with PackR
23
+ * Included syntax highlighting through `highlight.js`
22
24
  * AJAX comment system with PHP JSON file storage
23
- * Integration of the WMD-Markdown Editor for comments
25
+ * Integration of WMD-Markdown Editor for commenting
26
+
24
27
 
25
28
  ### Quickstart
26
29
 
27
- Install the gem by typing:
30
+ Install the gem:
31
+
28
32
  gem install shinmun
29
33
 
30
- Issue the following commands and the output will go to the public
31
- folder:
34
+ Download and extract the example blog from my [github repository][3].
35
+
36
+ Issue the following commands and you will see the blog on
37
+ `http://localhost:3000`:
38
+
39
+ cd shinmun-example
40
+ shinmun server
32
41
 
33
- cd example
34
- ../bin/shinmun
35
42
 
36
43
  ### Writing Posts
37
44
 
@@ -44,22 +51,38 @@ in `posts/2008/9/the-title-of-the-post.md`. After creating you will
44
51
  probably open the file, set the category and start writing your new
45
52
  article.
46
53
 
47
- After finishing your post, you may just run `shinmun` without arguments
48
- and the output will be rendered to the *public* folder.
54
+ Now you want to look at your rendered post. Just run:
55
+
56
+ shinmun server
57
+
58
+ Go to `http://localhost:3000` and you will see your blog served in
59
+ realtime. Just change and save any of your posts and you will see the
60
+ new output in your browser.
61
+
62
+ After finishing your post, you may run `shinmun render` and the output
63
+ will be rendered to the *public* folder.
64
+
65
+ By issuing the `shinmun push` command your blog will be pushed to your
66
+ server using rsync. This works only, if you define the blog_repository
67
+ variable inside blog.yml. It should contain something like
68
+ `user@myhost.com:/var/www/my-site/`.
49
69
 
50
70
 
51
71
  ### Post Format
52
72
 
53
73
  Each blog post is just a text file with an optional header section and
54
- a markup body, which are separated by a newline.
74
+ a markup body, which are separated by a newline. Normally you don't
75
+ have to worry about the post format, if you create posts with the
76
+ `shinmun new` command.
55
77
 
56
78
  The **first line** of the header should start with 3 dashes as usual
57
79
  for a YAML document.
58
80
 
59
- The **first and the second line** of the body becomes the title of the
60
- post.
81
+ The title of your post will be parsed from your first heading
82
+ according to the document type. Shinmun will try to figure out the
83
+ title for Markdown, Textile and HTML files.
61
84
 
62
- The header may have following attributes:
85
+ The yaml header may have following attributes:
63
86
 
64
87
  * `date`: post will show up in blog page and archive pages
65
88
  * `category`: post will show up in the defined category page
@@ -90,32 +113,50 @@ posts for comments.
90
113
 
91
114
  ### Directory layout
92
115
 
93
- * All your **posts** reside in the `posts` folder sorted by year/month.
116
+ * Your **assets** are in the `assets` folder, which gets copied to the
117
+ public folder in the render step.
94
118
 
95
- * All the **output** will be rendered to the `public` folder.
119
+ * The **settings of your blog** are defined in `config/blog.yml`
96
120
 
97
- * **Template** files are in the `templates` folder.
121
+ * Your **posts** reside in the `posts` folder sorted by year/month.
98
122
 
99
- * The **properties of your blog** defined in `posts/blog.yml`
123
+ * Your **pages** are located in the `pages` folder.
100
124
 
101
- * **Static files** should be put into the directories `public/images`,
102
- `public/stylesheets`, `public/javascripts`.
125
+ * **Template** files are in the `templates` folder.
126
+
127
+ * The **index page** of your blog is defined in `pages/index.rhtml` and
128
+ may be customized.
103
129
 
104
- * Archive pages will be rendered to files like `public/2008/9/index.html`.
130
+ * **Archive pages** will be rendered to files like `public/2008/9/index.html`.
105
131
 
106
- * Category pages will be rendered to files like `public/categories/ruby.html`.
132
+ * **Category pages** will be rendered to files like `public/categories/ruby.html`.
107
133
 
108
- * The *home page* of your blog will go to `public/index.html`.
109
134
 
110
135
  An example tree:
111
136
 
112
- + posts
137
+ + assets
138
+ + images
139
+ + stylesheets
140
+ + javascripts
141
+ + config
113
142
  + blog.yml
143
+ + pages
114
144
  + about.md
145
+ + index.rhtml
146
+ + posts
115
147
  + 2007
116
148
  + 2008
117
149
  + 9
118
150
  + my-article.md
151
+ + templates
152
+ + feed.rxml
153
+ + layout.rhtml
154
+ + page.rhtml
155
+ + post.rhtml
156
+ + posts.rhtml
157
+
158
+
159
+ The output will look like this:
119
160
 
120
161
  + public
121
162
  + index.html
@@ -131,13 +172,37 @@ An example tree:
131
172
  + stylesheets
132
173
  + javascripts
133
174
 
134
- + templates
135
- + feed.rxml
136
- + layout.rhtml
137
- + page.rhtml
138
- + post.rhtml
139
- + posts.rhtml
140
-
175
+
176
+ ### Config file
177
+
178
+ The configuration of the blog system consists of some variables
179
+ encoded as yaml file:
180
+
181
+ * blog_title: the title of your blog, used for rss
182
+
183
+ * blog_description: used for rss
184
+
185
+ * blog_language: used for rss
186
+
187
+ * blog_author: used for rss, acts also as fallback for the blog.author variable
188
+
189
+ * blog_url: used for rss
190
+
191
+ * blog_repository: path for rsync, used for `shinmun push` command
192
+
193
+ * base_path: if your blog should not be rendered to your site
194
+ root, you can define a sub path here (like `blog`)
195
+
196
+ * images_path: used for templates helper, defaults to `images`
197
+
198
+ * javascripts_path: used for templates helper, defaults to `javascripts`
199
+
200
+ * stylesheets_path: used for templates helper, defaults to `stylesheets`
201
+
202
+ * pack_javascripts: a list of scripts to be compressed
203
+
204
+ * pack_stylesheets: a list of stylesheets to be concatenated
205
+
141
206
 
142
207
  ### Layout
143
208
 
@@ -174,6 +239,10 @@ helpers. The helper methods will include a timestamp of the
174
239
  modification time as `querystring`, so that the browser will fetch the
175
240
  new resource if it has been changed.
176
241
 
242
+ If you want to define your own helpers, just define a file named
243
+ `templates/helpers.rb` with a module named `Shinmun::Helpers`. This
244
+ module will be included into the `Shinmun::Template` class.
245
+
177
246
 
178
247
  ### Post Template
179
248
 
@@ -195,34 +264,29 @@ The attributes of a post are accessible as instance variables in a template:
195
264
  </div>
196
265
 
197
266
 
198
-
199
267
  ### RSS Feeds
200
268
 
201
- Feeds will be rendered by one *ERB template*. Some of the variables
202
- have been read from the `blog.yml`, like `@blog_title`, other variables
203
- have been determined by the engine like `@posts` and `@category`.
204
-
205
- <?xml version="1.0" encoding="utf-8"?>
206
- <rss version="2.0">
207
- <channel>
208
- <title><%= @category ? @blog_title + ' - ' + @category : @blog_title %></title>
209
- <link><%= @blog_url %></link>
210
- <description><%= @category ? 'Category ' + @category : @blog_description %></description>
211
- <language><%= @blog_language %></language>
212
- <copyright><%= @blog_author %></copyright>
213
- <pubDate><%= rfc822 Time.now %></pubDate>
214
- <% for post in @posts %>
215
- <item>
216
- <title><%= post.title %></title>
217
- <description><%= post.text_summary %></description>
218
- <link><%= post.link %></link>
219
- <author><%= @blog_author %></author>
220
- <guid><%= post.guid %></guid>
221
- <pubDate><%= rfc822 post.date %></pubDate>
222
- </item>
223
- <% end %>
224
- </channel>
225
- </rss>
269
+ Feeds will be rendered by the *ERB template*
270
+ `templates/feed.rxml`. Some of the variables have been read from the
271
+ `blog.yml`, like `@blog_title`, other variables have been determined
272
+ by the engine like `@posts` or `@category`.
273
+
274
+
275
+ ### Packr Support
276
+
277
+ If you set the variables `pack_javascripts` or `pack_stylesheets`,
278
+ Shinmun will create the files `all.js` or `all.css` automatically
279
+ on rendering (even on each request of the webserver).
280
+
281
+ The Javascript will be compressed with Packr and for performance
282
+ reasons, minified versions for each of your javascripts will be
283
+ created automatically in `assets/javascripts`.
284
+
285
+ The stylesheets will be just concatenated to one file named `all.css`.
286
+
287
+ Note that you define a yaml array of filenames without file
288
+ extensions, so it should like `[jquery, jquery-form]`.
289
+
226
290
 
227
291
  ### Commenting System
228
292
 
@@ -247,3 +311,4 @@ Download or fork the package at my [github repository][1]
247
311
 
248
312
  [1]: http://github.com/georgi/shinmun/tree/master
249
313
  [2]: commenting-system-with-lightweight-json-store.html
314
+ [3]: http://github.com/georgi/shinmun-example/tree/master
data/Rakefile CHANGED
@@ -9,24 +9,23 @@ require 'fileutils'
9
9
 
10
10
  spec = Gem::Specification.new do |s|
11
11
  s.name = "shinmun"
12
- s.version = `git describe`.strip.sub(/-.*/, '')
12
+ s.version = `git describe`
13
13
  s.platform = Gem::Platform::RUBY
14
14
  s.summary = "a small blog engine"
15
15
 
16
16
  s.description = <<-EOF
17
17
  Shinmun is a blog engine, which renders text files using a markup
18
- language like Markdown and a set of templates into static web
19
- pages. It supports Categories, Archives and RSS Feeds. Commenting can
20
- be done with some Javascript, PHP and a flat file JSON store.
18
+ language like Markdown and a set of templates into either static web
19
+ pages or serving them over a rack adapter. Shinmun supports
20
+ categories, archives, rss feeds and commenting.
21
21
  EOF
22
22
 
23
- s.files = `git ls-files`.split("\n")
23
+ s.files = `git ls-files`.split("\n").reject { |f| f.match /^pkg/ }
24
24
  s.bindir = 'bin'
25
25
  s.executables << 'shinmun'
26
26
  s.require_path = 'lib'
27
- s.add_dependency 'uuid', '>=2.0.0'
28
- s.add_dependency 'BlueCloth', '>=1.0.0'
29
- s.add_dependency 'rubypants', '>=0.2.0'
27
+ s.add_dependency 'BlueCloth'
28
+ s.add_dependency 'rubypants'
30
29
  s.has_rdoc = true
31
30
  s.extra_rdoc_files = ['README.md']
32
31
 
@@ -51,10 +50,23 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
51
50
  '--charset' << 'utf-8'
52
51
  rdoc.rdoc_dir = "doc"
53
52
  rdoc.rdoc_files.include 'README.md'
54
- rdoc.rdoc_files.include('lib/shinmun.rb')
53
+ Dir['lib/**/*.rb'].each do |file|
54
+ rdoc.rdoc_files.include file
55
+ end
55
56
  end
56
57
 
57
58
 
58
- task :push => [:rdoc] do
59
- sh "rsync -avz doc/ mgeorgi@rack.rubyforge.org:/var/www/gforge-projects/shinmun"
59
+ task :pdoc => [:rdoc] do
60
+ sh "rsync -avz doc/ mgeorgi@rubyforge.org:/var/www/gforge-projects/shinmun"
61
+ end
62
+
63
+ desc "Publish the release files to RubyForge."
64
+ task :release => [ :gem ] do
65
+ require 'rubyforge'
66
+ require 'rake/contrib/rubyforgepublisher'
67
+
68
+ rubyforge = RubyForge.new
69
+ rubyforge.configure
70
+ rubyforge.login
71
+ rubyforge.add_release('shinmun', 'shinmun', spec.version, "pkg/shinmun-#{spec.version}.gem")
60
72
  end
data/bin/shinmun CHANGED
@@ -7,6 +7,11 @@ blog = Shinmun::Blog.new
7
7
  case ARGV[0]
8
8
  when 'new'
9
9
  blog.create_post(ARGV[1])
10
- else
10
+
11
+ when 'render'
11
12
  blog.write_all
13
+
14
+ when 'push'
15
+ blog.push
16
+
12
17
  end
@@ -0,0 +1,161 @@
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
@@ -0,0 +1,57 @@
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
@@ -0,0 +1,81 @@
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
+