shinmun 0.1 → 0.2

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.
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
+