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