automatic 12.3.0 → 12.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Gemfile +1 -0
  2. data/README.md +54 -36
  3. data/Rakefile +14 -0
  4. data/VERSION +1 -1
  5. data/automatic.gemspec +17 -5
  6. data/bin/automatic +37 -3
  7. data/bin/automatic-config +77 -0
  8. data/config/default.yml +9 -12
  9. data/doc/ChangeLog +32 -8
  10. data/doc/PLUGINS +205 -0
  11. data/doc/PLUGINS.ja +2 -3
  12. data/doc/README +488 -0
  13. data/doc/README.ja +195 -131
  14. data/lib/automatic/feed_parser.rb +1 -9
  15. data/lib/automatic/log.rb +1 -9
  16. data/lib/automatic/opml.rb +239 -0
  17. data/lib/automatic/pipeline.rb +16 -10
  18. data/lib/automatic/recipe.rb +3 -4
  19. data/lib/automatic.rb +32 -38
  20. data/lib/config/validator.rb +83 -0
  21. data/plugins/custom_feed/svn_log.rb +1 -1
  22. data/plugins/filter/ignore.rb +9 -1
  23. data/plugins/notify/ikachan.rb +7 -6
  24. data/plugins/publish/hatena_bookmark.rb +6 -9
  25. data/script/build +63 -0
  26. data/spec/lib/automatic/pipeline_spec.rb +55 -0
  27. data/spec/lib/automatic_spec.rb +77 -0
  28. data/spec/lib/pipeline_spec.rb +67 -0
  29. data/spec/plugins/filter/ignore_spec.rb +16 -0
  30. data/spec/plugins/filter/image_spec.rb +4 -4
  31. data/spec/plugins/filter/tumblr_resize_spec.rb +4 -4
  32. data/spec/plugins/notify/ikachan_spec.rb +30 -0
  33. data/spec/plugins/publish/console_spec.rb +1 -2
  34. data/spec/plugins/publish/hatena_bookmark_spec.rb +36 -1
  35. data/spec/plugins/store/full_text_spec.rb +0 -2
  36. data/spec/plugins/store/permalink_spec.rb +0 -1
  37. data/spec/plugins/store/target_link_spec.rb +0 -1
  38. data/spec/plugins/subscription/feed_spec.rb +0 -1
  39. data/spec/spec_helper.rb +6 -4
  40. data/spec/user_dir/plugins/store/mock.rb +12 -0
  41. data/test/fixtures/sampleOPML.xml +11 -0
  42. data/test/integration/test_activerecord.yml +2 -2
  43. data/test/integration/test_fulltext.yml +3 -3
  44. data/test/integration/test_hatenabookmark.yml +6 -2
  45. data/test/integration/test_ignore.yml +4 -1
  46. data/test/integration/test_ignore2.yml +1 -4
  47. data/test/integration/test_image2local.yml +3 -5
  48. data/test/integration/test_svnlog.yml +2 -1
  49. data/test/integration/test_tumblr2local.yml +3 -3
  50. metadata +43 -22
  51. data/utils/auto_discovery.rb +0 -18
  52. data/utils/opml_parser.rb +0 -247
data/doc/README.ja CHANGED
@@ -57,6 +57,121 @@ $ automatic -c config/feed2console.yml
57
57
  本ソフトウェアの動作確認に使うことができる。
58
58
 
59
59
 
60
+ ==========
61
+ レシピとは
62
+ ==========
63
+
64
+ Automatic Ruby では YAML 形式で書かれた設定ファイル
65
+ を解析し、呼び出すプラグインの情報とそれに伴う様々な
66
+ 情報を読み込む。
67
+
68
+ この YAML ファイルをレシピと呼ぶ。
69
+
70
+ automatic.rb 起動時に -c オプションの引数にレシピの
71
+ ファイル名を指定することができる。オプションが省略
72
+ された場合は config/default.yml が呼ばれる。
73
+
74
+ [起動例]
75
+ $ automatic -c ~/recipes/your_recipe.yml
76
+
77
+
78
+ ================
79
+ レシピの記述方法
80
+ ================
81
+
82
+ レシピには暗黙的な命名規則がある。
83
+
84
+ [書式]
85
+
86
+ plugins:
87
+ - module: プラグイン名
88
+ config:
89
+ 変数
90
+
91
+
92
+ 以下はレシピのサンプルとなる YAML ファイルである。
93
+
94
+ plugins:
95
+ - module: SubscriptionFeed
96
+ config:
97
+ feeds:
98
+ - http://reretlet.tumblr.com/rss
99
+
100
+ - module: StorePermalink
101
+ config:
102
+ db: tumblr.db
103
+
104
+ - module: FilterImage
105
+
106
+ - module: FilterTumblrResize
107
+
108
+ - module: StoreTargetLink
109
+ config:
110
+ path: /Users/yourname/Desktop/
111
+ interval: 1
112
+
113
+ このサンプルレシピの例では
114
+ 1. Subscription Feed で Tumblr のフィードを購読し
115
+ 2. StorePermalink でパーマリンクをデータベースに保存し
116
+ 3 FilterImage と FilterTumblrResize で画像の URL を指定して
117
+ 4. StoreTargetLink で Tumblr の画像をダウンロードする
118
+ という一連の処理をプラグインの組み合わせで実現している。
119
+
120
+
121
+ 以下に別の例を示す。
122
+
123
+ plugins:
124
+ - module: SubscriptionFeed
125
+ config:
126
+ feeds:
127
+ - http://example.com/rss2
128
+ - http://hogefuga.com/feed
129
+
130
+ - module: FilterIgnore
131
+ config:
132
+ link:
133
+ - hoge
134
+ - fuga
135
+
136
+ - module: StorePermalink
137
+ config:
138
+ db: permalink.db
139
+
140
+ - module: PublishHatenaBookmark
141
+ config:
142
+ username: your_hatena_id
143
+ password: your_password
144
+ interval: 5
145
+
146
+ このサンプルレシピの例では
147
+ 1. Subscription Feed でフィードを購読し
148
+ 2. FilterIgnore で無視キーワードを含む URL を除外し
149
+ 3. StorePermalink でパーマリンクをデータベースに保存し
150
+ 4. PublishHatenaBookmark ではてなブックマークをする
151
+ という一連の処理をプラグインの組み合わせで実現している。
152
+
153
+
154
+ このように、プラグインと設定情報をレシピに記述するだけで
155
+ 複数のプラグインの組み合わせ次第で無限の可能性を実現する
156
+ ことができる。
157
+
158
+
159
+ ==========
160
+ プラグイン
161
+ ==========
162
+
163
+ プラグインは後述する plugins ディレクトリにあるものが読み込まれる。
164
+
165
+ ~/.automatic/plugins が存在する場合、それらも同様に読み込まれる。
166
+
167
+ 自作したプラグインはホームディレクトリに置くと良い。
168
+ (プラグインの作り方は後述する)
169
+
170
+ 以下のコマンドで自作したプラグインのためのサブディレクトリを自動生成できる。
171
+ $ automatic-config scaffold
172
+ (automatic-config の説明は後述する)
173
+
174
+
60
175
  ==========================
61
176
  ディレクトリとファイル構成
62
177
  ==========================
@@ -65,7 +180,10 @@ $ automatic -c config/feed2console.yml
65
180
  +- bin
66
181
  | |
67
182
  | +- automatic
68
- | 実行ファイル本体
183
+ | | 実行ファイル本体
184
+ | |
185
+ | +- automatic-config
186
+ | 利用を補助するためのツール
69
187
  |
70
188
  +- config
71
189
  | |
@@ -80,13 +198,23 @@ $ automatic -c config/feed2console.yml
80
198
  |
81
199
  +- plugins
82
200
  | |
83
- | +- subscription フィードを購読するプラグイン
201
+ | +- subscription
202
+ | | フィードを購読するプラグイン
203
+ | |
204
+ | +- customfeed
205
+ | | カスタムフィードを生成するプラグイン
206
+ | |
207
+ | +- filter
208
+ | | 情報をフィルタリングするプラグイン
84
209
  | |
85
- | +- filter 情報をフィルタリングするプラグイン
210
+ | +- store
211
+ | | 情報を内部に保存するプラグイン
86
212
  | |
87
- | +- store 情報を内部に保存するプラグイン
213
+ | +- notify
214
+ | | 情報を通知するプラグイン
88
215
  | |
89
- | +- publish 外部に情報を送信するプラグイン
216
+ | +- publish
217
+ | 外部に情報を送信するプラグイン
90
218
  |
91
219
  | プラグインを開発した場合は plugins ディレクトリに置く
92
220
  |
@@ -97,23 +225,24 @@ $ automatic -c config/feed2console.yml
97
225
  | | Automatic モジュールの定義をする
98
226
  | |
99
227
  | +- automatic
100
- | |
101
- | +- environment.rb
102
- | | 環境情報を読み込む
103
- | |
104
- | +- pipeline.rb
105
- | | パイプラインと呼ばれるオブジェクトを利用し
106
- | | レシピに書かれたプラグインに処理を順次受け渡す
107
- | |
108
- | +- feed_parser.rb
109
- | | フィードを解析する
110
- | |
111
- | +- log.rb
112
- | | ログを出力する
113
- | |
114
- | +- recipe.rb
115
- | レシピを読み込む
116
- |
228
+ | | |
229
+ | | +- environment.rb
230
+ | | | 環境情報を読み込む
231
+ | | |
232
+ | | +- pipeline.rb
233
+ | | | パイプラインと呼ばれるオブジェクトを利用し
234
+ | | | レシピに書かれたプラグインに処理を順次受け渡す
235
+ | | |
236
+ | | +- feed_parser.rb
237
+ | | | フィードを解析する
238
+ | | |
239
+ | | +- log.rb
240
+ | | | ログを出力する
241
+ | | |
242
+ | | +- recipe.rb
243
+ | | レシピを読み込む
244
+ | |
245
+ | +- config
117
246
  |
118
247
  +- db
119
248
  | |
@@ -155,85 +284,6 @@ $ automatic -c config/feed2console.yml
155
284
  本ドキュメント
156
285
 
157
286
 
158
- ==========
159
- レシピとは
160
- ==========
161
-
162
- Automatic Ruby では YAML 形式で書かれた設定ファイル
163
- を解析し、呼び出すプラグインの情報とそれに伴う様々な
164
- 情報を読み込む。
165
-
166
- この YAML ファイルをレシピと呼ぶ。
167
-
168
- automatic.rb 起動時に -c オプションの引数にレシピの
169
- ファイル名を指定することができる。オプションが省略
170
- された場合は config/default.yml が呼ばれる。
171
-
172
- [起動例]
173
- $ automatic -c ~/recipes/your_recipe.yml
174
-
175
-
176
- ================
177
- レシピの記述方法
178
- ================
179
-
180
- レシピには暗黙的な命名規則がある。
181
- 以下はレシピのサンプルとなる YAML ファイルである。
182
-
183
- global:
184
- timezone: Asia/Tokyo
185
- cache:
186
- base: /tmp
187
- log:
188
- level: info
189
-
190
- plugins:
191
- - module: SubscriptionFeed
192
- config:
193
- feeds:
194
- - http://example.com/rss2
195
- - http://hogefuga.com/feed
196
-
197
- - module: FilterIgnore
198
- config:
199
- link:
200
- - hoge
201
- - fuga
202
-
203
- - module: StorePermalink
204
- config:
205
- db: permalink.db
206
-
207
- - module: PublishHatenaBookmark
208
- config:
209
- username: your_hatena_id
210
- password: your_password
211
- interval: 5
212
-
213
- このサンプルレシピの例では
214
- 1. フィードを購読し
215
- 2. 無視キーワードを含む URL を除外し
216
- 3. パーマリンクをデータベースに保存し
217
- 4. はてなブックマークをする
218
- という一連の処理をプラグインの組み合わせで実現している。
219
-
220
- このように、プラグインと設定情報をレシピに記述するだけで
221
- 複数のプラグインの組み合わせにより自由な処理を実現できる。
222
-
223
- たとえば Twitter に投稿する Publish::Twitter プラグインを
224
- 作成し上記のレシピに記述すれば、自動的に記事を Twitter に
225
- 投稿する自動処理が完成する。
226
-
227
- Gmail に送信する Publish::Gmail プラグインを作成しレシピ
228
- に追加すれば、フィードの記事を Gmail で読めるようになる。
229
-
230
- 他にもリンクを元にブログ記事全文を取得したり、電子書籍の
231
- 形式にして Kindle で読めるようにしたり、データベースに
232
- ブログ記事全文を保存してローカルのアプリで読めるように
233
- したりなど、プラグインの組み合わせ次第で無限の可能性を
234
- 実現することができる。
235
-
236
-
237
287
 
238
288
  ==============
239
289
  開発に参加する
@@ -245,6 +295,9 @@ https://github.com/id774/automaticruby
245
295
  課題
246
296
  https://github.com/id774/automaticruby/issues
247
297
 
298
+ RubyForge
299
+ http://rubyforge.org/projects/automatic/
300
+
248
301
  CI
249
302
  http://id774.net/jenkins/
250
303
 
@@ -350,21 +403,30 @@ end
350
403
  戻り値を検査する。
351
404
 
352
405
 
353
- ========================
354
- 継続的インテグレーション
355
- ========================
356
-
357
- CI は Jenkins でおこなう。
358
- http://id774.net/jenkins/
359
-
360
-
361
406
  ========
362
407
  結合試験
363
408
  ========
364
409
 
365
- test/integration 配下に複数のレシピを結合して
410
+ test/integration 配下に複数のプラグインを結合して
366
411
  テストをおこなうためのレシピを置く。
367
412
 
413
+ リポジトリにチェックインする前に必ず ruby 1.9 で
414
+ 以下のコマンドを実施し、テストビルドを実施する。
415
+
416
+ $ script/build
417
+
418
+ これによりすべての RSpec によるテスト、及び
419
+ 結合試験が実施される。途中で失敗することなくすべて
420
+ のテストが成功したことを確認した上でリポジトリに
421
+ チェックインする。さもなくば CI が失敗するだろう。
422
+
423
+
424
+ ========================
425
+ 継続的インテグレーション
426
+ ========================
427
+
428
+ CI は Jenkins でおこなう。
429
+ http://id774.net/jenkins/
368
430
 
369
431
 
370
432
  ================
@@ -375,34 +437,35 @@ doc/PLUGINS.ja を参照
375
437
 
376
438
 
377
439
 
378
- ====================
379
- その他のツールの説明
380
- ====================
381
-
382
- utils の下にはフィードを扱うための補助的なツールが
383
- 置かれる。内容は以下のとおりである。
384
-
440
+ ================
441
+ automatic-config
442
+ ================
385
443
 
386
- 名前
387
- utils/opml_parser.rb
444
+ automatic-config は補助的なツールである。
388
445
 
389
- 説明
390
- 既存の RSS Reader の OPML から登録済みフィードの
391
- URL リストを抽出するのに使う。
446
+ [書式]
447
+ $ automatic-config
448
+ サブコマンドの一覧を表示する
392
449
 
393
- $ ruby opml_parser.rb > feeds.txt
450
+ $ automatic-config scaffold
451
+ ホームディレクトリに ~/.automatic ディレクトリを生成する。
394
452
 
453
+ $ automatic-config autodiscovery <url>
454
+ 対象の URL をオートディスカバリで探知しフィードの URL を返す。
455
+ (例)
456
+ $ automatic-config autodiscovery http://id774.net/blog
457
+ ["http://id774.net/blog/feed/", "http://id774.net/blog/comments/feed/"]
395
458
 
396
- 名前
397
- utils/auto_discovery.rb
459
+ $ automatic-config opmlparser <opml path>
460
+ OPML ファイルを解析し URL を出力する。
461
+ (例)
462
+ $ automatic-config opmlparser opml.xml > feeds.txt
398
463
 
399
- 説明
400
- 引数に指定した URL から購読対象のフィードを
401
- オートディスカバリで抽出する。
464
+ $ automatic-config feedparser <url>
465
+ フィードを解析して内容を返す。
402
466
 
403
- $ ruby auto_discovery.rb "https://twitter.com/twitt"
404
- ["https://twitter.com/statuses/user_timeline/4441691.rss",
405
- "https://twitter.com/favorites/4441691.rss"]
467
+ $ automatic-config log <level> <message>
468
+ Automatic Ruby のログ形式でメッセージを出力する。
406
469
 
407
470
 
408
471
 
@@ -436,6 +499,7 @@ hashie
436
499
  activerecord
437
500
  gcalapi
438
501
  xml-simple
502
+ feedbag
439
503
 
440
504
  詳細は Gemfile を参照
441
505
 
@@ -1,9 +1,8 @@
1
- #!/usr/bin/env ruby
2
1
  # -*- coding: utf-8 -*-
3
2
  # Name:: Automatic::FeedParser
4
3
  # Author:: 774 <http://id774.net>
5
4
  # Created:: Feb 19, 2012
6
- # Updated:: Feb 24, 2012
5
+ # Updated:: Mar 11, 2012
7
6
  # Copyright:: 774 Copyright (c) 2012
8
7
  # License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
9
8
 
@@ -27,10 +26,3 @@ module Automatic
27
26
  end
28
27
  end
29
28
  end
30
-
31
- if __FILE__ == $0
32
- url = ARGV.shift || abort("Usage: feed_parser.rb <url>")
33
- rss_results = Automatic::FeedParser.get_rss(url)
34
- require 'pp'
35
- pp links
36
- end
data/lib/automatic/log.rb CHANGED
@@ -1,9 +1,8 @@
1
- #!/usr/bin/env ruby
2
1
  # -*- coding: utf-8 -*-
3
2
  # Name:: Automatic::Log
4
3
  # Author:: 774 <http://id774.net>
5
4
  # Created:: Feb 20, 2012
6
- # Updated:: Feb 24, 2012
5
+ # Updated:: Mar 11, 2012
7
6
  # Copyright:: 774 Copyright (c) 2012
8
7
  # License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
9
8
 
@@ -15,10 +14,3 @@ module Automatic
15
14
  end
16
15
  end
17
16
  end
18
-
19
- if __FILE__ == $0
20
- level = ARGV.shift || abort("Usage: log.rb <level> <message>")
21
- message = ARGV.shift
22
- Automatic::Log.puts(level, message)
23
- end
24
-
@@ -0,0 +1,239 @@
1
+ require 'rexml/document'
2
+ require 'rexml/parsers/pullparser'
3
+
4
+ module Automatic
5
+ module OPML
6
+ def self::unnormalize(text)
7
+ text.gsub(/&(\w+);/) {
8
+ x = $1
9
+ case x
10
+ when 'lt'
11
+ '<'
12
+ when 'gt'
13
+ '>'
14
+ when 'quot'
15
+ '"'
16
+ when 'amp'
17
+ '&'
18
+ when 'apos'
19
+ "'"
20
+ when /^\#(\d+)$/
21
+ [$1.to_i].pack("U")
22
+ when /^\#x([0-9a-f]+)$/i
23
+ [$1.hex].pack("U")
24
+ else
25
+ raise "unnormalize error '#{x}'"
26
+ end
27
+ }
28
+ end
29
+
30
+ module DOM
31
+ class Element
32
+ include Enumerable
33
+
34
+ def initialize
35
+ @attr = {}
36
+ @parent = nil
37
+ @children = []
38
+ end
39
+ attr_accessor :parent
40
+ attr_reader :attr, :children
41
+
42
+ def each
43
+ yield self
44
+ @children.each {|c|
45
+ c.each {|x|
46
+ yield x
47
+ }
48
+ }
49
+ end
50
+
51
+ def <<(elem)
52
+ elem.parent = self if elem.respond_to? :parent=
53
+ @children << elem
54
+ self
55
+ end
56
+
57
+ def [](n)
58
+ if n.is_a? String
59
+ @attr[n]
60
+ else
61
+ @children[n]
62
+ end
63
+ end
64
+
65
+ def []=(k,v)
66
+ if k.is_a? String
67
+ @attr[k] = v
68
+ else
69
+ @children[k] = v
70
+ end
71
+ end
72
+
73
+ def text
74
+ @attr['text']
75
+ end
76
+
77
+ def type
78
+ @attr['type']
79
+ end
80
+
81
+ def is_comment?
82
+ @attr['isComment']=='true'
83
+ end
84
+
85
+ def is_breaakpoint?
86
+ @attr['isBreakpoint']=='true'
87
+ end
88
+ end # Element
89
+
90
+ class OPML < Element
91
+ def head
92
+ @children.find {|x| x.is_a? Head }
93
+ end
94
+
95
+ def body
96
+ @children.find {|x| x.is_a? Body }
97
+ end
98
+ end
99
+
100
+ class Head < Element
101
+ end
102
+
103
+ class Body < Element
104
+ def outline
105
+ @children.find {|x| x.is_a? Outline }
106
+ end
107
+ end
108
+
109
+ class Outline < Element
110
+ def method_missing(name,*arg,&block)
111
+ name = name.to_s
112
+ case name
113
+ when /^[a-z][0-9a-zA-Z_]*$/
114
+ key = name.gsub(/_([a-z])/) {|x| ".#{$1.upcase}" }
115
+ @attr[key]
116
+ else
117
+ raise NoMethodError
118
+ end
119
+ end
120
+ end
121
+ end # Model
122
+
123
+ class Parser
124
+ def initialize(port)
125
+ @p = REXML::Parsers::PullParser.new(port)
126
+ @opml = nil
127
+ end
128
+
129
+ def parse_tree
130
+ root = cur = nil
131
+ while event=@p.pull
132
+ case event.event_type
133
+ when :xmldecl
134
+ encoding = event[1]
135
+ when :start_element
136
+ case event[0]
137
+ when "opml"
138
+ root = cur = DOM::OPML.new
139
+ when "head"
140
+ e = DOM::Head.new
141
+ cur << e
142
+ cur = e
143
+ when "body"
144
+ e = DOM::Body.new
145
+ cur << e
146
+ cur = e
147
+ when "outline"
148
+ e = DOM::Outline.new
149
+ cur << e
150
+ cur = e
151
+ else
152
+ cur['.' + event[0]] = read_text
153
+ end
154
+ event[1].each {|k,v|
155
+ cur[k] = OPML::unnormalize(v)
156
+ }
157
+ when :end_element
158
+ cur = cur.parent
159
+ when :text
160
+ when :cdata
161
+ when :start_doctype
162
+ when :end_doctype,:comment
163
+ when :end_document
164
+ break
165
+ else
166
+ p event.event_type
167
+ p event
168
+ raise "unknown event"
169
+ end
170
+ end # while
171
+ root
172
+ end # parse_tree
173
+
174
+ def each_outline
175
+ root = cur = nil
176
+ while event=@p.pull
177
+ case event.event_type
178
+ when :xmldecl
179
+ encoding = event[1]
180
+ when :start_element
181
+ case event[0]
182
+ when "opml"
183
+ root = cur = DOM::OPML.new
184
+ when "head"
185
+ e = DOM::Head.new
186
+ cur << e
187
+ cur = e
188
+ when "body"
189
+ e = DOM::Body.new
190
+ cur << e
191
+ cur = e
192
+ when "outline"
193
+ e = DOM::Outline.new
194
+ # cur << e
195
+ cur = e
196
+ else
197
+ cur['_' + event[0]] = read_text
198
+ end
199
+ event[1].each {|k,v|
200
+ cur[k] = OPML::unnormalize(v)
201
+ }
202
+ if event[0]=="outline"
203
+ yield root, cur
204
+ end
205
+ when :end_element
206
+ cur = cur.parent if cur.kind_of? DOM::Element
207
+ when :text
208
+ when :cdata
209
+ when :start_doctype
210
+ when :end_doctype,:comment
211
+ when :end_document
212
+ break
213
+ else
214
+ p event.event_type
215
+ p event
216
+ raise "unknown event"
217
+ end
218
+ end # while
219
+ end # each_outline
220
+
221
+ def read_text
222
+ text = ""
223
+ while event = @p.pull
224
+ case event.event_type
225
+ when :end_element
226
+ break
227
+ when :text
228
+ text << OPML::unnormalize(event[0])
229
+ when :cdata
230
+ text << event[0]
231
+ else
232
+ raise
233
+ end
234
+ end
235
+ text
236
+ end
237
+ end # Parser
238
+ end # OPML
239
+ end