shinmun 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README.md +127 -62
- data/Rakefile +23 -11
- data/bin/shinmun +6 -1
- data/lib/shinmun/admin_controller.rb +161 -0
- data/lib/shinmun/aggregations/delicious.rb +57 -0
- data/lib/shinmun/aggregations/flickr.rb +81 -0
- data/lib/shinmun/blog.rb +319 -0
- data/lib/shinmun/cache.rb +59 -0
- data/lib/shinmun/comment.rb +44 -0
- data/lib/shinmun/controller.rb +135 -0
- data/lib/shinmun/helpers.rb +116 -0
- data/lib/shinmun/post.rb +166 -0
- data/lib/shinmun/template.rb +39 -0
- data/lib/shinmun.rb +14 -411
- metadata +15 -81
- data/example/posts/2008/9/example.md +0 -19
- data/example/posts/blog.yml +0 -10
- data/example/posts/uuid.state +0 -3
- data/example/public/controllers/comments.php +0 -56
- data/example/public/images/loading.gif +0 -0
- data/example/public/javascripts/comments.js +0 -60
- data/example/public/javascripts/highlight.js +0 -505
- data/example/public/javascripts/images/bg-fill.png +0 -0
- data/example/public/javascripts/images/bg.png +0 -0
- data/example/public/javascripts/images/blockquote.png +0 -0
- data/example/public/javascripts/images/bold.png +0 -0
- data/example/public/javascripts/images/code.png +0 -0
- data/example/public/javascripts/images/h1.png +0 -0
- data/example/public/javascripts/images/hr.png +0 -0
- data/example/public/javascripts/images/img.png +0 -0
- data/example/public/javascripts/images/italic.png +0 -0
- data/example/public/javascripts/images/link.png +0 -0
- data/example/public/javascripts/images/ol.png +0 -0
- data/example/public/javascripts/images/redo.png +0 -0
- data/example/public/javascripts/images/separator.png +0 -0
- data/example/public/javascripts/images/ul.png +0 -0
- data/example/public/javascripts/images/undo.png +0 -0
- data/example/public/javascripts/images/wmd-on.png +0 -0
- data/example/public/javascripts/images/wmd.png +0 -0
- data/example/public/javascripts/jquery-form.js +0 -869
- data/example/public/javascripts/jquery.js +0 -3383
- data/example/public/javascripts/languages/1c.js +0 -82
- data/example/public/javascripts/languages/axapta.js +0 -52
- data/example/public/javascripts/languages/bash.js +0 -80
- data/example/public/javascripts/languages/diff.js +0 -64
- data/example/public/javascripts/languages/dos.js +0 -33
- data/example/public/javascripts/languages/dynamic.js +0 -460
- data/example/public/javascripts/languages/ini.js +0 -36
- data/example/public/javascripts/languages/javascript.js +0 -38
- data/example/public/javascripts/languages/lisp.js +0 -86
- data/example/public/javascripts/languages/mel.js +0 -50
- data/example/public/javascripts/languages/profile.js +0 -50
- data/example/public/javascripts/languages/renderman.js +0 -71
- data/example/public/javascripts/languages/smalltalk.js +0 -53
- data/example/public/javascripts/languages/sql.js +0 -50
- data/example/public/javascripts/languages/static.js +0 -175
- data/example/public/javascripts/languages/vbscript.js +0 -25
- data/example/public/javascripts/languages/www.js +0 -245
- data/example/public/javascripts/prettyDate.js +0 -36
- data/example/public/javascripts/showdown.js +0 -421
- data/example/public/javascripts/template.js +0 -165
- data/example/public/javascripts/wmd-base.js +0 -1799
- data/example/public/javascripts/wmd-plus.js +0 -311
- data/example/public/javascripts/wmd.js +0 -73
- data/example/public/stylesheets/grid.css +0 -243
- data/example/public/stylesheets/grid.png +0 -0
- data/example/public/stylesheets/highlight/ascetic.css +0 -38
- data/example/public/stylesheets/highlight/dark.css +0 -96
- data/example/public/stylesheets/highlight/default.css +0 -91
- data/example/public/stylesheets/highlight/far.css +0 -95
- data/example/public/stylesheets/highlight/idea.css +0 -75
- data/example/public/stylesheets/highlight/sunburst.css +0 -112
- data/example/public/stylesheets/highlight/zenburn.css +0 -108
- data/example/public/stylesheets/print.css +0 -76
- data/example/public/stylesheets/reset.css +0 -45
- data/example/public/stylesheets/style.css +0 -141
- data/example/public/stylesheets/typography.css +0 -59
- data/example/templates/feed.rxml +0 -21
- data/example/templates/layout.rhtml +0 -54
- data/example/templates/page.rhtml +0 -4
- data/example/templates/post.rhtml +0 -57
- data/example/templates/posts.rhtml +0 -10
data/.gitignore
CHANGED
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
|
-
|
19
|
-
|
20
|
-
*
|
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
|
25
|
+
* Integration of WMD-Markdown Editor for commenting
|
26
|
+
|
24
27
|
|
25
28
|
### Quickstart
|
26
29
|
|
27
|
-
Install the gem
|
30
|
+
Install the gem:
|
31
|
+
|
28
32
|
gem install shinmun
|
29
33
|
|
30
|
-
|
31
|
-
|
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
|
-
|
48
|
-
|
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
|
60
|
-
|
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
|
-
*
|
116
|
+
* Your **assets** are in the `assets` folder, which gets copied to the
|
117
|
+
public folder in the render step.
|
94
118
|
|
95
|
-
*
|
119
|
+
* The **settings of your blog** are defined in `config/blog.yml`
|
96
120
|
|
97
|
-
* **
|
121
|
+
* Your **posts** reside in the `posts` folder sorted by year/month.
|
98
122
|
|
99
|
-
*
|
123
|
+
* Your **pages** are located in the `pages` folder.
|
100
124
|
|
101
|
-
* **
|
102
|
-
|
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
|
-
+
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
202
|
-
have been read from the
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
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
|
20
|
-
|
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 '
|
28
|
-
s.add_dependency '
|
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
|
-
|
53
|
+
Dir['lib/**/*.rb'].each do |file|
|
54
|
+
rdoc.rdoc_files.include file
|
55
|
+
end
|
55
56
|
end
|
56
57
|
|
57
58
|
|
58
|
-
task :
|
59
|
-
sh "rsync -avz doc/ mgeorgi@
|
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
@@ -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
|
+
|