td2planet 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +3 -0
- data/MIT-LICENSE +20 -0
- data/README +63 -0
- data/README.ja +66 -0
- data/bin/td2planet.rb +10 -0
- data/config.yaml +57 -0
- data/data/td2planet/templates/day.rhtml +30 -0
- data/data/td2planet/templates/footer.rhtml +30 -0
- data/data/td2planet/templates/header.rhtml +1 -0
- data/data/td2planet/templates/layout.rhtml +33 -0
- data/data/td2planet/templates/opml.rxml +19 -0
- data/data/td2planet/templates/section.rhtml +23 -0
- data/lib/td2planet/default_formatter.rb +23 -0
- data/lib/td2planet/fetcher.rb +63 -0
- data/lib/td2planet/formatter.rb +224 -0
- data/lib/td2planet/runner.rb +60 -0
- data/lib/td2planet/sample_formatter.rb +26 -0
- data/lib/td2planet/version.rb +30 -0
- data/lib/td2planet/writer.rb +52 -0
- metadata +70 -0
data/ChangeLog
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Kazuhiro NISHIYAMA
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= TD2Planet
|
2
|
+
This is a planet implementation of ruby, mainly for tdiary.
|
3
|
+
|
4
|
+
See http://www.planetplanet.org about planet.
|
5
|
+
|
6
|
+
== Install
|
7
|
+
=== Install using setup.rb
|
8
|
+
1. ruby setup.rb
|
9
|
+
|
10
|
+
=== Install using rubygems
|
11
|
+
1. gem build *.gemspec
|
12
|
+
2. gem install *.gem
|
13
|
+
|
14
|
+
== Usage
|
15
|
+
1. copy and edit config.yaml
|
16
|
+
2. run "td2planet.rb config.yaml"
|
17
|
+
3. copy output files to public directory if need be
|
18
|
+
4. set cron and so on if need be
|
19
|
+
|
20
|
+
=== Usage without install
|
21
|
+
1. copy and edit config.yaml
|
22
|
+
2. run "ruby -I lib bin/td2planet.rb config.yaml"
|
23
|
+
3. same as above
|
24
|
+
|
25
|
+
== SECURITY NOTICE
|
26
|
+
Do not add untrusted feeds.
|
27
|
+
All content:encoded in feeds output to the file of output_html as is,
|
28
|
+
even if content:encoded includes scripts and so on.
|
29
|
+
|
30
|
+
To avoid security problem, I recommend to divide the domain of
|
31
|
+
this planet from the domains of other contents.
|
32
|
+
(e.g. planet.example.jp)
|
33
|
+
|
34
|
+
== Customize Output
|
35
|
+
After customize, run "td2planet.rb -n config.yaml" to update output files.
|
36
|
+
|
37
|
+
=== Customize Template
|
38
|
+
1. make override templates directory
|
39
|
+
2. copy the file under templates to the override templates directory
|
40
|
+
3. edit the file
|
41
|
+
4. add the override templates directory to templates_path in config.yaml
|
42
|
+
|
43
|
+
=== Customize Formatter
|
44
|
+
1. copy default_formatter.rb or sample_formatter.rb to ./your_formatter.rb
|
45
|
+
2. edit the file (you must change class name from DefaultFormatter or SampleFormatter to YourFormatter if the filename is your_formatter.rb)
|
46
|
+
|
47
|
+
== Q&A
|
48
|
+
[Q. Why do this planet outputs content:encoded as is?]
|
49
|
+
A. I can not keep safe white lists and/or black lists.
|
50
|
+
(see one of white lists: http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%C0%A5%A4%A5%A2%A5%EA%A1%BCXSS%C2%D0%BA%F6 (Japanese))
|
51
|
+
[Q. Why do not output feeds include contents?]
|
52
|
+
A. If you use other feed reader, you can import original feeds
|
53
|
+
from opml to the feed reader.
|
54
|
+
|
55
|
+
== License
|
56
|
+
setup.rb:: GNU LGPL
|
57
|
+
other files:: MIT-LICENSE
|
58
|
+
|
59
|
+
Copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
60
|
+
|
61
|
+
--
|
62
|
+
# -*- coding: utf-8 -*-
|
63
|
+
# vim: ts=2 sw=2 sts=2 fenc=utf-8:
|
data/README.ja
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= TD2Planet
|
2
|
+
これはrubyによるplanet実装です。
|
3
|
+
主にtDiaryのmakerss.rbプラグインの出力を扱う用に調整してあります。
|
4
|
+
|
5
|
+
planetとは何かについては http://www.planetplanet.org などを
|
6
|
+
参照してください。
|
7
|
+
|
8
|
+
== インストール
|
9
|
+
=== setup.rbを使ったインストール
|
10
|
+
1. ruby setup.rb
|
11
|
+
|
12
|
+
=== rubygemsを使ったインストール
|
13
|
+
1. gem build *.gemspec
|
14
|
+
2. gem install *.gem
|
15
|
+
|
16
|
+
== 使い方
|
17
|
+
1. config.yamlを適当なところにコピーして編集
|
18
|
+
2. td2planet.rb config.yaml
|
19
|
+
3. 必要なら出力されたファイルを公開ディレクトリにコピー
|
20
|
+
4. 必要ならcronなどで定期的に実行
|
21
|
+
|
22
|
+
=== インストールせずに使う方法
|
23
|
+
1. config.yamlを編集
|
24
|
+
2. ruby -I lib bin/td2planet.rb config.yaml
|
25
|
+
3. 以降は上と同じ
|
26
|
+
|
27
|
+
== セキュリティ上の注意
|
28
|
+
信頼できないRSSは設定しないでください。
|
29
|
+
output_htmlで指定したファイルには元のRSSのcontent:encodedの内容が、
|
30
|
+
スクリプトなどを含んでいたとしても、そのまま出力されます。
|
31
|
+
|
32
|
+
セキュリティ上の問題を避けるため、このplanetのドメインを他の
|
33
|
+
コンテンツのドメインとわけることをお薦めします。
|
34
|
+
(例えば planet.example.jp)
|
35
|
+
|
36
|
+
|
37
|
+
== 出力のカスタマイズ
|
38
|
+
カスタマイズ後、"td2planet.rb -n config.yaml"で出力を更新できます。
|
39
|
+
|
40
|
+
=== テンプレートのカスタマイズ
|
41
|
+
1. カスタマイズするテンプレート用のディレクトリを作成
|
42
|
+
2. カスタマイズしたいテンプレートファイルをtemplates以下からコピー
|
43
|
+
3. コピーしたテンプレートファイルを編集
|
44
|
+
4. カスタマイズしたテンプレート用のディレクトリをconfig.yamlのtemplates_pathに追加
|
45
|
+
|
46
|
+
=== Formatterのカスタマイズ
|
47
|
+
1. default_formatter.rbかsample_formatter.rbを./your_formatter.rbなどにコピー
|
48
|
+
2. ファイルを編集(DefaultFormatterかSampleFormatterをファイル名がyour_formatter.rbならYourFormatterに変更する必要あり)
|
49
|
+
|
50
|
+
== Q&A
|
51
|
+
[Q. なぜcontent:encodedをそのまま出力?]
|
52
|
+
A. 安全なホワイトリストやブラックリストを保守できないため。
|
53
|
+
(ホワイトリストの例: http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%C0%A5%A4%A5%A2%A5%EA%A1%BCXSS%C2%D0%BA%F6 )
|
54
|
+
[Q. RSSに内容をいれていないのはなぜ?]
|
55
|
+
A. 他のフィードリーダーを使っているのなら、opmlを使って
|
56
|
+
元のフィードをインポートして購読できるから。
|
57
|
+
|
58
|
+
== License
|
59
|
+
setup.rb:: GNU LGPL
|
60
|
+
other files:: MIT-LICENSE
|
61
|
+
|
62
|
+
Copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
63
|
+
|
64
|
+
--
|
65
|
+
# -*- coding: utf-8 -*-
|
66
|
+
# vim: ts=2 sw=2 sts=2 fenc=utf-8:
|
data/bin/td2planet.rb
ADDED
data/config.yaml
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
## general config
|
2
|
+
title: TD2Planet Sample
|
3
|
+
base_uri: http://planet.example.jp/
|
4
|
+
|
5
|
+
## feed URIs
|
6
|
+
uri:
|
7
|
+
- http://kazuhiko.tdiary.net/index.rdf
|
8
|
+
- http://sho.tdiary.net/index.rdf
|
9
|
+
- http://www.tdiary.net/index.rdf
|
10
|
+
- http://www.tdiary.org/index.rdf
|
11
|
+
|
12
|
+
## feeds cache into cache_dir
|
13
|
+
cache_dir: cache
|
14
|
+
|
15
|
+
## generated files into output_dir
|
16
|
+
output_dir: output
|
17
|
+
output_html: planet.html
|
18
|
+
|
19
|
+
## themes
|
20
|
+
#tdiary_theme_path: http://localhost/tdiary/theme
|
21
|
+
tdiary_theme_path: /var/www/tdiary/theme
|
22
|
+
tdiary_theme: light-blue
|
23
|
+
|
24
|
+
## parts of templates
|
25
|
+
#author: Your name
|
26
|
+
#made: mailto:webmaster@example.jp
|
27
|
+
#favicon: /favicon.ico
|
28
|
+
#logo_html: <img src="http://planet.example.jp/images/logo.png" width="168" height="150" alt="">
|
29
|
+
date_strftime_format: '%Y-%m-%d'
|
30
|
+
sanchor_strftime_format: '%H:%M:%S'
|
31
|
+
|
32
|
+
## templates search path
|
33
|
+
#templates_path:
|
34
|
+
# - /path/to/override/templates
|
35
|
+
# - /path/to/other/override/templates
|
36
|
+
|
37
|
+
## formatter
|
38
|
+
formatter: default_formatter
|
39
|
+
#formatter: sample_formatter
|
40
|
+
#formatter: your_formatter
|
41
|
+
|
42
|
+
## spam filter (default_formatter feature)
|
43
|
+
## (filtered if last value is true, output if false)
|
44
|
+
filter: |
|
45
|
+
if (/ツッコミ/ =~ k(item.title) &&
|
46
|
+
(
|
47
|
+
(k(item.content_encoded).scan(/http:/).size >= 5) ||
|
48
|
+
(k(item.description).scan(/http:/).size >= 5) ||
|
49
|
+
(/\[\/url\]/ =~ k(item.content_encoded)) ||
|
50
|
+
(/\[\/url\]/ =~ k(item.description)) ||
|
51
|
+
(/★/ =~ k(item.dc_creator)) ||
|
52
|
+
/@google\.com/ =~ k(item.dc_creator)
|
53
|
+
))
|
54
|
+
true
|
55
|
+
else
|
56
|
+
false
|
57
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<hr class="sep">
|
2
|
+
<div class="day">
|
3
|
+
<h2>
|
4
|
+
<span class="date"><%=h date_format(items[0]) %></span>
|
5
|
+
<span class="title">
|
6
|
+
<% if rss.channel.link -%>
|
7
|
+
<a href="<%=hk rss.channel.link %>"><%=hk rss.channel.title %></a>
|
8
|
+
<% else -%>
|
9
|
+
<%=hk rss.channel.title %>
|
10
|
+
<% end -%>
|
11
|
+
<% if rss.channel.dc_creator -%>
|
12
|
+
(<%=hk rss.channel.dc_creator %>)
|
13
|
+
<% end -%>
|
14
|
+
</span>
|
15
|
+
</h2>
|
16
|
+
<div class="body">
|
17
|
+
<% items.each do |item| -%>
|
18
|
+
<%= section(item, rss) -%>
|
19
|
+
<% end -%>
|
20
|
+
</div>
|
21
|
+
<% if /./ =~ rss.channel.dc_rights.to_s -%>
|
22
|
+
<div class="comment">
|
23
|
+
<!--<div class="caption"></div>-->
|
24
|
+
<div class="commentshort">
|
25
|
+
<!--<p><span class="canchor"></span><span class="commentator"></span> [...]</p>-->
|
26
|
+
<p><%=hk rss.channel.dc_rights %></p>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
<% end -%>
|
30
|
+
</div>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
</div>
|
2
|
+
<div class="sidebar">
|
3
|
+
<% if @config['logo_html'] -%>
|
4
|
+
<%= @config['logo_html'] %>
|
5
|
+
<% end -%>
|
6
|
+
<div class="block">
|
7
|
+
<h3>Subscriptions</h3>
|
8
|
+
<ul>
|
9
|
+
<% @rss_list.sort_by{|rss| -rss.item.date.to_i }.each do |rss| -%>
|
10
|
+
<li>
|
11
|
+
<a href="<%=hk rss.channel.link %>" title="<%=hk rss.channel.description %>"><%=hk rss.channel.title %></a>
|
12
|
+
<a href="<%=hk rss.channel.about %>">(feed)</a>
|
13
|
+
</li>
|
14
|
+
<% end -%>
|
15
|
+
</ul>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class="block">
|
19
|
+
<h3>Other formats</h3>
|
20
|
+
<ul>
|
21
|
+
<li><a href="rss10.xml">RSS 1.0</a></li>
|
22
|
+
<li><a href="rss20.xml">RSS 2.0</a></li>
|
23
|
+
<li><a href="opml.xml">opml</a></li>
|
24
|
+
</ul>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<div class="block">
|
28
|
+
<p>Last updated: <%= Time.now.localtime.strftime('%Y-%m-%d %H:%M:%S') %></p>
|
29
|
+
</div>
|
30
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<div class="main">
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
2
|
+
<html>
|
3
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
4
|
+
<meta name="generator" content="<%=h TD2Planet.generator %>">
|
5
|
+
<meta http-equiv="content-script-type" content="text/plain"><!-- wish to disable scripts -->
|
6
|
+
<% if @config['author'] -%>
|
7
|
+
<meta name="author" content="<%=h @config['author'] %>">
|
8
|
+
<% end -%>
|
9
|
+
<% if @config['made'] -%>
|
10
|
+
<link rev="made" href="<%=h @config['made'] %>">
|
11
|
+
<% end -%>
|
12
|
+
<% if @config['favicon'] -%>
|
13
|
+
<link rel="shortcut icon" href="<%=h @config['favicon'] %>" type="image/x-icon">
|
14
|
+
<% end -%>
|
15
|
+
<meta http-equiv="content-style-type" content="text/css">
|
16
|
+
<link rel="stylesheet" href="<%=h @config['tdiary_theme_path'] %>/base.css" type="text/css" media="all">
|
17
|
+
<link rel="stylesheet" href="<%=h @config['tdiary_theme_path'] %>/<%=h @config['tdiary_theme'] %>/<%=h @config['tdiary_theme'] %>.css" title="<%=h @config['tdiary_theme'] %>" type="text/css" media="all">
|
18
|
+
<title><%=h @config['title'] %></title>
|
19
|
+
</head>
|
20
|
+
<body>
|
21
|
+
<h1><%=h @config['title'] %></h1>
|
22
|
+
<%= header %>
|
23
|
+
<% days.each do |day| -%>
|
24
|
+
<%= move_tukkomi_link(day(day[:items], day[:rss])) %>
|
25
|
+
<% end -%>
|
26
|
+
<hr class="sep">
|
27
|
+
<%= footer %>
|
28
|
+
<div class="footer">
|
29
|
+
Generated by <%=h TD2Planet.td2planet_version %><br>
|
30
|
+
Powered by <%=h TD2Planet.ruby_version %>
|
31
|
+
</div>
|
32
|
+
</body>
|
33
|
+
</html>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<opml version="1.0">
|
3
|
+
<head>
|
4
|
+
<title><%=h @config['title'] %></title>
|
5
|
+
<dateCreated><%=h Time.now %></dateCreated>
|
6
|
+
<dateModified><%=h Time.now %></dateModified>
|
7
|
+
<% if @config.key?('author') -%>
|
8
|
+
<ownerName><%=h @config['author'] %></ownerName>
|
9
|
+
<% end -%>
|
10
|
+
<% if @config.key?('made') -%>
|
11
|
+
<ownerEmail><%=h @config['made'].sub(/^mailto:/, '') %></ownerEmail>
|
12
|
+
<% end -%>
|
13
|
+
</head>
|
14
|
+
<body>
|
15
|
+
<% rss_list.sort_by{|rss| -rss.item.date.to_i }.each do |rss| -%>
|
16
|
+
<outline text="<%=hk rss.channel.title %>" type="link" xmlUrl="<%=hk rss.channel.link %>"/>
|
17
|
+
<% end -%>
|
18
|
+
</body>
|
19
|
+
</opml>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<div class="section">
|
2
|
+
<%
|
3
|
+
section_body = to_section_body(item)
|
4
|
+
# tdiary makerss.rb plugin
|
5
|
+
if /<h3/ =~ section_body
|
6
|
+
-%>
|
7
|
+
<%=
|
8
|
+
section_body.sub(/<h3.*?>/) do
|
9
|
+
$& + to_sanchor(item) # + to_categories(item) # categories in original h3
|
10
|
+
end.sub(/<\/h3>/) do
|
11
|
+
to_author(item) + $&
|
12
|
+
end
|
13
|
+
%>
|
14
|
+
<% else -%>
|
15
|
+
<h3>
|
16
|
+
<%= to_sanchor(item) %>
|
17
|
+
<%= to_categories(item) %>
|
18
|
+
<%=hk item.title %>
|
19
|
+
<%= to_author(item) %>
|
20
|
+
</h3>
|
21
|
+
<%= to_section_body(item) %>
|
22
|
+
<% end -%>
|
23
|
+
</div>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#--
|
2
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
3
|
+
# vim: set filetype=ruby ts=2 sw=2 sts=2 fenc=utf-8:
|
4
|
+
#
|
5
|
+
# copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
6
|
+
#++
|
7
|
+
|
8
|
+
require 'td2planet/formatter'
|
9
|
+
|
10
|
+
module TD2Planet
|
11
|
+
class DefaultFormatter < Formatter
|
12
|
+
def initialize(config)
|
13
|
+
super
|
14
|
+
unless @config.key?('filter')
|
15
|
+
@config['filter'] = 'true'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def skip?(item)
|
20
|
+
eval(@config['filter']) or too_old?(item)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#--
|
2
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
3
|
+
# vim: set filetype=ruby ts=2 sw=2 sts=2 fenc=utf-8:
|
4
|
+
#
|
5
|
+
# copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
6
|
+
#++
|
7
|
+
|
8
|
+
require 'erb'
|
9
|
+
require 'pathname'
|
10
|
+
require 'rss'
|
11
|
+
require 'uri'
|
12
|
+
|
13
|
+
module TD2Planet
|
14
|
+
class Fetcher
|
15
|
+
def initialize(cache_dir, dry_run=false)
|
16
|
+
@cache_dir = Pathname.new(cache_dir)
|
17
|
+
unless @cache_dir.exist?
|
18
|
+
@cache_dir.mkdir
|
19
|
+
end
|
20
|
+
@dry_run = dry_run
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_all_rss(uris)
|
24
|
+
rss_list = []
|
25
|
+
uris.each do |uri|
|
26
|
+
cache_file = @cache_dir + ERB::Util.u(uri)
|
27
|
+
if @dry_run
|
28
|
+
puts "use cache: #{cache_file}"
|
29
|
+
text = cache_file.read
|
30
|
+
else
|
31
|
+
text = nil
|
32
|
+
begin
|
33
|
+
puts "fetch: #{uri}"
|
34
|
+
text = URI.parse(uri).read
|
35
|
+
rescue Timeout::Error
|
36
|
+
# fallback
|
37
|
+
puts "ERROR: timeout #{uri}"
|
38
|
+
text = cache_file.read
|
39
|
+
rescue Exception
|
40
|
+
puts "ERROR: #{$!} (#{$!.class}) on #{uri}"
|
41
|
+
raise
|
42
|
+
else
|
43
|
+
if text.status[0] == '200' && /rss/ =~ text
|
44
|
+
cache_file.open('wb'){|f| f.write(text) }
|
45
|
+
else
|
46
|
+
# fallback
|
47
|
+
puts "ERROR: fetch failed #{uri} #{text.status}"
|
48
|
+
text = cache_file.read
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
text = fixup_rss(text)
|
53
|
+
rss_list << RSS::Parser.parse(text, false)
|
54
|
+
end
|
55
|
+
rss_list
|
56
|
+
end
|
57
|
+
|
58
|
+
# euc-jp may fail to parse
|
59
|
+
def fixup_rss(text)
|
60
|
+
text.sub(/\bencoding="euc-jp"/ni, 'encoding="euc-jp-ms"')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
#--
|
2
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
3
|
+
# vim: set filetype=ruby ts=2 sw=2 sts=2 fenc=utf-8:
|
4
|
+
#
|
5
|
+
# copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
6
|
+
#++
|
7
|
+
|
8
|
+
require 'erb'
|
9
|
+
require 'nkf'
|
10
|
+
require 'uri'
|
11
|
+
require 'rss/maker'
|
12
|
+
|
13
|
+
module TD2Planet
|
14
|
+
class Formatter
|
15
|
+
include ERB::Util
|
16
|
+
|
17
|
+
ERB_METHODS = []
|
18
|
+
def self.def_erb_method(method_name, fname=nil)
|
19
|
+
if /\A\w+/ =~ method_name
|
20
|
+
fname ||= "#{$&}.rhtml"
|
21
|
+
end
|
22
|
+
ERB_METHODS << [method_name, fname]
|
23
|
+
end
|
24
|
+
def_erb_method('layout(days)')
|
25
|
+
def_erb_method('day(items, rss)')
|
26
|
+
def_erb_method('section(item, rss)')
|
27
|
+
def_erb_method('header()')
|
28
|
+
def_erb_method('footer()')
|
29
|
+
|
30
|
+
def k(s)
|
31
|
+
NKF.nkf('-wm0', s)
|
32
|
+
end
|
33
|
+
def hk(s)
|
34
|
+
h(k(s))
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_templates_dir
|
38
|
+
basename = 'layout.rhtml'
|
39
|
+
dir = File.expand_path('../../data/td2planet/templates', File.dirname(__FILE__))
|
40
|
+
if File.exist?(File.join(dir, basename))
|
41
|
+
return dir
|
42
|
+
end
|
43
|
+
|
44
|
+
require 'rbconfig'
|
45
|
+
dir = File.expand_path('td2planet/templates', Config::CONFIG['datadir'])
|
46
|
+
if File.exist?(File.join(dir, basename))
|
47
|
+
return dir
|
48
|
+
end
|
49
|
+
raise "not found templates"
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(config)
|
53
|
+
@config = config
|
54
|
+
@config['title'] ||= '(no title Planet)'
|
55
|
+
@config['tdiary_theme_path'] ||= '/tdiary/theme'
|
56
|
+
@config['tdiary_theme'] ||= 'default'
|
57
|
+
@config['date_strftime_format'] ||= '%Y-%m-%d'
|
58
|
+
@config['sanchor_strftime_format'] ||= '%H:%M:%S'
|
59
|
+
@base_uri = URI.parse(@config['base_uri'])
|
60
|
+
@config['templates_path'] ||= []
|
61
|
+
@config['templates_path'].push(default_templates_dir)
|
62
|
+
ERB_METHODS.each do |method_name, basename|
|
63
|
+
@config['templates_path'].find do |dir|
|
64
|
+
fname = File.expand_path(basename, dir)
|
65
|
+
if File.exist?(fname)
|
66
|
+
puts "use template #{basename}: #{fname}"
|
67
|
+
erb = ERB.new(File.read(fname), nil, '-')
|
68
|
+
eval("def self.#{method_name}\n#{erb.src}\nend\n", binding, fname, 0)
|
69
|
+
true
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def date_format(item)
|
78
|
+
item.date.localtime.strftime(@config['date_strftime_format'])
|
79
|
+
end
|
80
|
+
def sanchor_format(item)
|
81
|
+
item.date.localtime.strftime(@config['sanchor_strftime_format'])
|
82
|
+
end
|
83
|
+
|
84
|
+
def too_old?(item, sec=7*24*60*60)
|
85
|
+
item.date < Time.now - sec
|
86
|
+
end
|
87
|
+
|
88
|
+
# override
|
89
|
+
def skip?(item)
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_html(rss_list)
|
94
|
+
@rss_list = rss_list
|
95
|
+
day_rss = {}
|
96
|
+
rss_list.each do |rss|
|
97
|
+
next unless rss.items
|
98
|
+
rss.items.each do |item|
|
99
|
+
next if skip?(item)
|
100
|
+
day = (day_rss[[date_format(item), rss]] ||= Array.new)
|
101
|
+
day.push(item)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
days = []
|
105
|
+
day_rss.keys.sort_by do |date, rss|
|
106
|
+
date
|
107
|
+
end.reverse_each do |key|
|
108
|
+
date, rss = key
|
109
|
+
items = day_rss[key]
|
110
|
+
items = items.sort_by do |item|
|
111
|
+
# tdiary makerss plugin generates same time entries
|
112
|
+
item.date.to_s + item.link
|
113
|
+
end
|
114
|
+
days << {:items => items, :rss => rss}
|
115
|
+
end
|
116
|
+
days = days.sort_by do |day|
|
117
|
+
-day[:items].collect{|item| item.date.to_i}.max
|
118
|
+
end
|
119
|
+
layout(days)
|
120
|
+
end
|
121
|
+
|
122
|
+
def relative_path_to_absolute_uri(attr_value, base_uri)
|
123
|
+
uri = URI.parse(attr_value)
|
124
|
+
if uri.scheme.nil?
|
125
|
+
URI.parse(base_uri) + uri
|
126
|
+
else
|
127
|
+
uri
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def tag_attr_relative_path_to_absolute_uri(tag, attr_name, base_uri)
|
132
|
+
tag.gsub!(/#{attr_name}=([\"\'])([^\"\']+)\1/i) do
|
133
|
+
%Q!#{attr_name}=#{$1}#{relative_path_to_absolute_uri($2, base_uri)}#{$1}!
|
134
|
+
end or tag.gsub!(/#{attr_name}=(\S+)/) do
|
135
|
+
%Q!#{attr_name}=#{relative_path_to_absolute_uri($1, base_uri)}!
|
136
|
+
end
|
137
|
+
tag
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_section_body(item)
|
141
|
+
if item.content_encoded
|
142
|
+
k(item.content_encoded).gsub(/<([aA]\b[\s\S]+?)>/) do
|
143
|
+
a = tag_attr_relative_path_to_absolute_uri($1, "href", item.link)
|
144
|
+
%Q!<#{a} rel="nofollow">!
|
145
|
+
#end.gsub(/<img\b[\s\S]+?>/i) do
|
146
|
+
# tag_attr_relative_path_to_absolute_uri($&, "src", item.link)
|
147
|
+
end.gsub(/<img\b[\s\S]+?>/i) do
|
148
|
+
img = $&
|
149
|
+
case img
|
150
|
+
when /alt=([\"\'])(.+?)\1/
|
151
|
+
$2
|
152
|
+
when /alt=(\S+?)/
|
153
|
+
$1
|
154
|
+
else
|
155
|
+
"[img]"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
else
|
159
|
+
'<p>' + h(k(item.description)).gsub(/\r?\n/, '<br>') + '</p>'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_sanchor(item)
|
164
|
+
%Q!<a href="#{hk item.link}"><span class="sanchor">#{h sanchor_format(item)}</span></a> !
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_categories(item)
|
168
|
+
h(item.dc_subjects.collect{|s|"[#{k(s.content)}]" if /./ =~ s.content})
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_author(item)
|
172
|
+
if item.dc_creator
|
173
|
+
" (#{hk(item.dc_creator)})"
|
174
|
+
else
|
175
|
+
""
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
TukkomiLinkRe = /^<p><a href="(.+)">ツッコミを入れる<\/a><\/p>$/
|
180
|
+
def move_tukkomi_link(html)
|
181
|
+
if TukkomiLinkRe =~ html
|
182
|
+
tukkomi_link = $&
|
183
|
+
tukkomi_uri = $1
|
184
|
+
re = Regexp.new(Regexp.quote(tukkomi_link))
|
185
|
+
tukkomi_moved_html = html.gsub(re, '')
|
186
|
+
re = Regexp.new(Regexp.quote('<!--<div class="caption"></div>-->'))
|
187
|
+
tukkomi_moved_html.sub!(re) { %Q|<div class="caption">[<a href="#{tukkomi_uri}">ツッコミを入れる</a>]</div>| }
|
188
|
+
# other day tukkomi_link found
|
189
|
+
if TukkomiLinkRe =~ tukkomi_moved_html
|
190
|
+
html.gsub!(TukkomiLinkRe) { %Q|<div class="caption">[<a href="#{$1}">ツッコミを入れる</a>]</div>| }
|
191
|
+
else
|
192
|
+
html = tukkomi_moved_html
|
193
|
+
end
|
194
|
+
end
|
195
|
+
html
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
def_erb_method('to_opml(rss_list)', 'opml.rxml')
|
200
|
+
|
201
|
+
def to_rss(rss_list, version='1.0', basename='rss.xml')
|
202
|
+
RSS::Maker.make(version) do |maker|
|
203
|
+
maker.channel.about = @base_uri + basename
|
204
|
+
maker.channel.title = @config['title']
|
205
|
+
maker.channel.link = @base_uri
|
206
|
+
maker.channel.description = "#{@base_uri} - #{@config['title']}"
|
207
|
+
|
208
|
+
maker.items.do_sort = true
|
209
|
+
|
210
|
+
rss_list.each do |rss|
|
211
|
+
rss.items.each do |item|
|
212
|
+
next if skip?(item)
|
213
|
+
new_item = maker.items.new_item
|
214
|
+
%w"link title date".each do |attr|
|
215
|
+
value = item.__send__(attr)
|
216
|
+
value = k(value) if value.is_a?(String)
|
217
|
+
new_item.__send__("#{attr}=", value)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#--
|
2
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
3
|
+
# vim: set filetype=ruby ts=2 sw=2 sts=2 fenc=utf-8:
|
4
|
+
#
|
5
|
+
# copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
6
|
+
#++
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
require 'td2planet/fetcher'
|
10
|
+
require 'td2planet/formatter'
|
11
|
+
require 'td2planet/writer'
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
module TD2Planet
|
15
|
+
module_function
|
16
|
+
|
17
|
+
def main(argv=ARGV)
|
18
|
+
opts = OptionParser.new
|
19
|
+
usage = proc { puts opts; exit(1) }
|
20
|
+
opts.banner << " config.yaml"
|
21
|
+
@dry_run = false
|
22
|
+
opts.on('-n', '--dry-run', 'do not fetch, generate files only') { @dry_run = true }
|
23
|
+
opts.on('-h', '--help', 'show this message') { usage.call }
|
24
|
+
opts.parse!(argv)
|
25
|
+
if argv.empty?
|
26
|
+
usage.call
|
27
|
+
end
|
28
|
+
argv.each do |filename|
|
29
|
+
config = YAML.load(File.read(filename))
|
30
|
+
run(config)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_formatter_class(formatter_name)
|
35
|
+
formatter_class_name = formatter_name.gsub(/(^|_)(.)/) { $2.upcase }
|
36
|
+
begin
|
37
|
+
require formatter_name
|
38
|
+
rescue LoadError
|
39
|
+
begin
|
40
|
+
require "td2planet/#{formatter_name}"
|
41
|
+
rescue LoadError
|
42
|
+
raise LoadError, "no such formatter: #{formatter_name}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
formatter = const_get(formatter_class_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def run(config)
|
49
|
+
formatter_name = config['formatter'] || 'default_formatter'
|
50
|
+
formatter = get_formatter_class(formatter_name).new(config)
|
51
|
+
|
52
|
+
writer = Writer.new(config, formatter)
|
53
|
+
fetcher = Fetcher.new(config['cache_dir'], @dry_run)
|
54
|
+
rss_list = fetcher.fetch_all_rss(config['uri'])
|
55
|
+
|
56
|
+
writer.output_html(rss_list)
|
57
|
+
writer.output_opml(rss_list)
|
58
|
+
writer.output_rss(rss_list)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#--
|
2
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
3
|
+
# vim: set filetype=ruby ts=2 sw=2 sts=2 fenc=utf-8:
|
4
|
+
#
|
5
|
+
# copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
6
|
+
#++
|
7
|
+
|
8
|
+
require 'td2planet/formatter'
|
9
|
+
|
10
|
+
module TD2Planet
|
11
|
+
class SampleFormatter < Formatter
|
12
|
+
def spam?(item)
|
13
|
+
if /ツッコミ/ =~ k(item.title) &&
|
14
|
+
/casino/ =~ item.dc_creator &&
|
15
|
+
/casino/ =~ item.description
|
16
|
+
true
|
17
|
+
else
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def skip?(item)
|
23
|
+
spam?(item) or too_old?(item)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#--
|
2
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
3
|
+
# vim: set filetype=ruby ts=2 sw=2 sts=2 fenc=utf-8:
|
4
|
+
#
|
5
|
+
# copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
6
|
+
#++
|
7
|
+
|
8
|
+
module TD2Planet
|
9
|
+
TD2PLANET_VERSION = "0.1.0"
|
10
|
+
TD2PLANET_RELEASE_DATE = "2007-02-22"
|
11
|
+
|
12
|
+
# return ruby version string (simulate output of ruby -v)
|
13
|
+
def self.ruby_version
|
14
|
+
if defined?(RUBY_PATCHLEVEL)
|
15
|
+
"ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]"
|
16
|
+
else
|
17
|
+
"ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# return TD2Planet version string
|
22
|
+
def self.td2planet_version
|
23
|
+
"TD2Planet #{TD2PLANET_VERSION} (#{TD2PLANET_RELEASE_DATE})"
|
24
|
+
end
|
25
|
+
|
26
|
+
# return string for meta generator (see templates/layout.rhtml)
|
27
|
+
def self.generator
|
28
|
+
"#{td2planet_version} / Powered by #{ruby_version}"
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#--
|
2
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
3
|
+
# vim: set filetype=ruby ts=2 sw=2 sts=2 fenc=utf-8:
|
4
|
+
#
|
5
|
+
# copyright (c) 2006, 2007 Kazuhiro NISHIYAMA
|
6
|
+
#++
|
7
|
+
|
8
|
+
require 'pathname'
|
9
|
+
|
10
|
+
module TD2Planet
|
11
|
+
class Writer
|
12
|
+
def initialize(config, formatter)
|
13
|
+
@config = config
|
14
|
+
@output_dir ||= Pathname.new(config['output_dir'])
|
15
|
+
unless @output_dir.exist?
|
16
|
+
@output_dir.mkdir
|
17
|
+
end
|
18
|
+
@formatter = formatter
|
19
|
+
end
|
20
|
+
|
21
|
+
def output_html(rss_list)
|
22
|
+
if @config.key?('output_html')
|
23
|
+
output_html = @output_dir + @config['output_html']
|
24
|
+
else
|
25
|
+
output_html = @output_dir + 'index.html'
|
26
|
+
end
|
27
|
+
|
28
|
+
output_html.open('wb') do |f|
|
29
|
+
f.write(@formatter.to_html(rss_list))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def output_opml(rss_list)
|
34
|
+
output_opml = @output_dir + 'opml.xml'
|
35
|
+
output_opml.open('wb') do |f|
|
36
|
+
f.write(@formatter.to_opml(rss_list))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def output_rss(rss_list)
|
41
|
+
[
|
42
|
+
['1.0', 'rss10.xml'],
|
43
|
+
['2.0', 'rss20.xml'],
|
44
|
+
].each do |rss_version, basename|
|
45
|
+
output_rss = @output_dir + basename
|
46
|
+
output_rss.open('wb') do |f|
|
47
|
+
f.write(@formatter.to_rss(rss_list, rss_version, basename))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.2
|
3
|
+
specification_version: 1
|
4
|
+
name: td2planet
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-02-22 00:00:00 +09:00
|
8
|
+
summary: planet of ruby, mainly for tdiary
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: zn@mbf.nifty.com
|
12
|
+
homepage: http://rubyforge.org/projects/td2planet/
|
13
|
+
rubyforge_project: td2planet
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Kazuhiro NISHIYAMA
|
31
|
+
files:
|
32
|
+
- ChangeLog
|
33
|
+
- MIT-LICENSE
|
34
|
+
- README
|
35
|
+
- README.ja
|
36
|
+
- bin/td2planet.rb
|
37
|
+
- config.yaml
|
38
|
+
- data/td2planet/templates/day.rhtml
|
39
|
+
- data/td2planet/templates/footer.rhtml
|
40
|
+
- data/td2planet/templates/header.rhtml
|
41
|
+
- data/td2planet/templates/layout.rhtml
|
42
|
+
- data/td2planet/templates/opml.rxml
|
43
|
+
- data/td2planet/templates/section.rhtml
|
44
|
+
- lib/td2planet/default_formatter.rb
|
45
|
+
- lib/td2planet/fetcher.rb
|
46
|
+
- lib/td2planet/formatter.rb
|
47
|
+
- lib/td2planet/runner.rb
|
48
|
+
- lib/td2planet/sample_formatter.rb
|
49
|
+
- lib/td2planet/version.rb
|
50
|
+
- lib/td2planet/writer.rb
|
51
|
+
test_files: []
|
52
|
+
|
53
|
+
rdoc_options:
|
54
|
+
- --charset
|
55
|
+
- utf-8
|
56
|
+
- --inline-source
|
57
|
+
- --line-numbers
|
58
|
+
- --main
|
59
|
+
- README
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README
|
62
|
+
- README.ja
|
63
|
+
executables:
|
64
|
+
- td2planet.rb
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
dependencies: []
|
70
|
+
|