automatic 12.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +23 -0
- data/README.md +109 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/app.rb +15 -0
- data/automatic.gemspec +122 -0
- data/bin/automatic +15 -0
- data/config/default.yml +29 -0
- data/config/feed2console.yml +15 -0
- data/db/.gitkeep +0 -0
- data/doc/COPYING +674 -0
- data/doc/ChangeLog +17 -0
- data/doc/PLUGINS.ja +205 -0
- data/doc/README.ja +449 -0
- data/lib/automatic.rb +60 -0
- data/lib/automatic/environment.rb +8 -0
- data/lib/automatic/feed_parser.rb +36 -0
- data/lib/automatic/log.rb +24 -0
- data/lib/automatic/pipeline.rb +36 -0
- data/lib/automatic/recipe.rb +31 -0
- data/lib/config/validator.rb +83 -0
- data/plugins/custom_feed/svn_log.rb +56 -0
- data/plugins/filter/ignore.rb +60 -0
- data/plugins/filter/image.rb +47 -0
- data/plugins/filter/tumblr_resize.rb +40 -0
- data/plugins/notify/ikachan.rb +85 -0
- data/plugins/publish/console.rb +31 -0
- data/plugins/publish/google_calendar.rb +86 -0
- data/plugins/publish/hatena_bookmark.rb +103 -0
- data/plugins/store/full_text.rb +57 -0
- data/plugins/store/permalink.rb +47 -0
- data/plugins/store/store_database.rb +53 -0
- data/plugins/store/target_link.rb +47 -0
- data/plugins/subscription/feed.rb +30 -0
- data/script/bootstrap +117 -0
- data/spec/plugins/custom_feed/svn_log_spec.rb +31 -0
- data/spec/plugins/filter/ignore_spec.rb +37 -0
- data/spec/plugins/filter/image_spec.rb +55 -0
- data/spec/plugins/filter/tumblr_resize_spec.rb +102 -0
- data/spec/plugins/notify/ikachan_spec.rb +28 -0
- data/spec/plugins/publish/console_spec.rb +24 -0
- data/spec/plugins/publish/google_calendar_spec.rb +82 -0
- data/spec/plugins/publish/hatena_bookmark_spec.rb +36 -0
- data/spec/plugins/store/full_text_spec.rb +39 -0
- data/spec/plugins/store/permalink_spec.rb +39 -0
- data/spec/plugins/store/target_link_spec.rb +30 -0
- data/spec/plugins/subscription/feed_spec.rb +36 -0
- data/spec/spec_helper.rb +82 -0
- data/test/integration/test_activerecord.yml +24 -0
- data/test/integration/test_fulltext.yml +24 -0
- data/test/integration/test_hatenabookmark.yml +26 -0
- data/test/integration/test_ignore.yml +22 -0
- data/test/integration/test_ignore2.yml +25 -0
- data/test/integration/test_image2local.yml +26 -0
- data/test/integration/test_svnlog.yml +14 -0
- data/test/integration/test_tumblr2local.yml +26 -0
- data/utils/auto_discovery.rb +18 -0
- data/utils/opml_parser.rb +247 -0
- data/vendor/.gitkeep +0 -0
- metadata +205 -0
data/lib/automatic.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Name:: Automatic::Ruby
|
3
|
+
# Author:: 774 <http://id774.net>
|
4
|
+
# Version:: 12.02.1-devel
|
5
|
+
# Created:: Feb 18, 2012
|
6
|
+
# Updated:: Mar 2, 2012
|
7
|
+
# Copyright:: 774 Copyright (c) 2012
|
8
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
|
+
|
10
|
+
module Automatic
|
11
|
+
require 'automatic/environment'
|
12
|
+
require 'automatic/recipe'
|
13
|
+
require 'automatic/pipeline'
|
14
|
+
require 'automatic/log'
|
15
|
+
require 'automatic/feed_parser'
|
16
|
+
|
17
|
+
VERSION = "12.02.1-devel"
|
18
|
+
|
19
|
+
def self.root_dir
|
20
|
+
@root_dir
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.plugins_dir
|
24
|
+
@root_dir + "/plugins/"
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.config_dir
|
28
|
+
@root_dir + "/config/"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.run(root_dir)
|
32
|
+
@root_dir = root_dir
|
33
|
+
recipe_path = ""
|
34
|
+
require 'optparse'
|
35
|
+
parser = OptionParser.new { |parser|
|
36
|
+
parser.banner = "Usage: automatic [options] arg"
|
37
|
+
parser.version = VERSION
|
38
|
+
parser.separator "options:"
|
39
|
+
parser.on('-c', '--config FILE', String,
|
40
|
+
"recipe YAML file"){|c| recipe_path = c}
|
41
|
+
parser.on('-h', '--help', "show this message") {
|
42
|
+
puts parser
|
43
|
+
exit
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
begin
|
48
|
+
parser.parse!
|
49
|
+
print "Loading #{recipe_path}\n" unless recipe_path == ""
|
50
|
+
rescue OptionParser::ParseError => err
|
51
|
+
$stderr.puts err.message
|
52
|
+
$stderr.puts parser.help
|
53
|
+
exit 1
|
54
|
+
end
|
55
|
+
|
56
|
+
# recipe treat as an object.
|
57
|
+
recipe = Automatic::Recipe.new(recipe_path)
|
58
|
+
Automatic::Pipeline.run(recipe)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
# Set up gems listed in the Gemfile.
|
5
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
6
|
+
|
7
|
+
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
|
8
|
+
Bundler.require :default if defined?(Bundler)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Name:: Automatic::FeedParser
|
4
|
+
# Author:: 774 <http://id774.net>
|
5
|
+
# Created:: Feb 19, 2012
|
6
|
+
# Updated:: Feb 24, 2012
|
7
|
+
# Copyright:: 774 Copyright (c) 2012
|
8
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
|
+
|
10
|
+
module Automatic
|
11
|
+
module FeedParser
|
12
|
+
require 'rss'
|
13
|
+
require 'uri'
|
14
|
+
|
15
|
+
def self.get_rss(url)
|
16
|
+
begin
|
17
|
+
unless url.nil?
|
18
|
+
feed = URI.parse(url).normalize
|
19
|
+
open(feed) { |http|
|
20
|
+
response = http.read
|
21
|
+
RSS::Parser.parse(response, false)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
rescue => e
|
25
|
+
raise e
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Name:: Automatic::Log
|
4
|
+
# Author:: 774 <http://id774.net>
|
5
|
+
# Created:: Feb 20, 2012
|
6
|
+
# Updated:: Feb 24, 2012
|
7
|
+
# Copyright:: 774 Copyright (c) 2012
|
8
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
|
+
|
10
|
+
module Automatic
|
11
|
+
module Log
|
12
|
+
def self.puts(level, message)
|
13
|
+
t = Time.now.strftime("%Y/%m/%d %X")
|
14
|
+
print "#{t} [#{level}] #{message}\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
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,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Name:: Automatic::Pipeline
|
4
|
+
# Author:: 774 <http://id774.net>
|
5
|
+
# Created:: Feb 22, 2012
|
6
|
+
# Updated:: Mar 3, 2012
|
7
|
+
# Copyright:: 774 Copyright (c) 2012
|
8
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
|
+
|
10
|
+
require 'active_support/core_ext'
|
11
|
+
|
12
|
+
module Automatic
|
13
|
+
module Plugin end
|
14
|
+
|
15
|
+
module Pipeline
|
16
|
+
def self.load_plugin(module_name)
|
17
|
+
Dir.foreach(Automatic.plugins_dir) do |subdir|
|
18
|
+
if /^#{subdir}_(.*)$/ =~ module_name.underscore
|
19
|
+
path = Automatic.plugins_dir + subdir + "/#{$1}.rb"
|
20
|
+
Automatic::Plugin.autoload module_name.to_sym, path.to_s
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run(recipe)
|
26
|
+
raise "NoRecipeError" if recipe.nil?
|
27
|
+
pipeline = []
|
28
|
+
recipe.each_plugin { |plugin|
|
29
|
+
load_plugin(plugin.module)
|
30
|
+
klass = Automatic::Plugin.const_get(plugin.module)
|
31
|
+
pipeline = klass.new(plugin.config, pipeline).run
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Name:: Automatic::Recipe
|
2
|
+
# Author:: 774 <http://id774.net>
|
3
|
+
# Version:: 12.02-devel
|
4
|
+
# Created:: Feb 18, 2012
|
5
|
+
# Updated:: Feb 24, 2012
|
6
|
+
# Copyright:: 774 Copyright (c) 2012
|
7
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
8
|
+
|
9
|
+
require 'hashie'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
module Automatic
|
13
|
+
class Recipe
|
14
|
+
attr_reader :procedure
|
15
|
+
|
16
|
+
def initialize(path)
|
17
|
+
path = Automatic.config_dir + 'default.yml' if path.to_s.empty?
|
18
|
+
load_recipe(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_recipe(path)
|
22
|
+
@procedure = Hashie::Mash.new(YAML.load(File.read(path)))
|
23
|
+
end
|
24
|
+
|
25
|
+
def each_plugin
|
26
|
+
@procedure.plugins.each { |plugin|
|
27
|
+
yield plugin
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Name:: Automatic::Config::Validator
|
4
|
+
# Author:: aerith <http://aerith.sc/>
|
5
|
+
# Created:: Feb 23, 2012
|
6
|
+
# Updated:: Feb 23, 2012
|
7
|
+
# Copyright:: Copyright (c) 2012
|
8
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
|
+
|
10
|
+
class Automatic
|
11
|
+
module Config
|
12
|
+
class Validator
|
13
|
+
attr_accessor :config, :result
|
14
|
+
|
15
|
+
class InvalidException < Exception; end;
|
16
|
+
|
17
|
+
def initialize(config = nil)
|
18
|
+
@config = config || {}
|
19
|
+
@result = ValidateResult.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate(klass, config = nil)
|
23
|
+
return true if config.nil?
|
24
|
+
return true if not config.is_a?(Hash)
|
25
|
+
return true if not klass.respond_to?(:rules)
|
26
|
+
|
27
|
+
rules = klass.rules
|
28
|
+
return true if not rules.is_a?(Hash)
|
29
|
+
|
30
|
+
process(rules, config).result.valid?
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def process(rules, config)
|
35
|
+
errors = {}
|
36
|
+
|
37
|
+
rules.each do |key, rule|
|
38
|
+
name = key.to_sym
|
39
|
+
|
40
|
+
rule.each do |method, args|
|
41
|
+
unless ValidateRule.send(method, *[config, name, args])
|
42
|
+
errors[name] = {} if errors[name].nil? or errors.size < 1
|
43
|
+
errors[name][method] = true
|
44
|
+
if @config["stop_on_invalid"] and errors[name][method]
|
45
|
+
raise InvalidException
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@result.errors = errors
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
class ValidateResult
|
57
|
+
attr_accessor :errors
|
58
|
+
|
59
|
+
def error(name)
|
60
|
+
errors.exists?(name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def valid?
|
64
|
+
errors.empty?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module ValidateRule
|
69
|
+
def self.required(config, name, *args)
|
70
|
+
return true unless args.size < 1 or args.first
|
71
|
+
return true if not config[name.to_s].nil? and not config[name.to_s].empty?
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.default(config, name, *args)
|
76
|
+
config[name.to_s] ||= args.first
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Name:: Automatic::Plugin::CustomFeed::SVNLog
|
3
|
+
# Author:: kzgs
|
4
|
+
# Created:: Feb 29, 2012
|
5
|
+
# Updated:: Mar 3, 2012
|
6
|
+
# Copyright:: kzgs Copyright (c) 2012
|
7
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
8
|
+
|
9
|
+
require 'rss/maker'
|
10
|
+
|
11
|
+
module Automatic::Plugin
|
12
|
+
class CustomFeedSVNLog
|
13
|
+
require 'xmlsimple'
|
14
|
+
|
15
|
+
def initialize(config, pipeline=[])
|
16
|
+
@config = config
|
17
|
+
@pipeline = pipeline
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
revisions = XmlSimple.xml_in(`svn log #{svn_log_argument}`)["logentry"]
|
22
|
+
@pipeline << RSS::Maker.make("1.0") { |maker|
|
23
|
+
maker.channel.title = @config["title"] || ""
|
24
|
+
maker.channel.about = ""
|
25
|
+
maker.channel.description = ""
|
26
|
+
maker.channel.link = base_url
|
27
|
+
revisions.each { |rev|
|
28
|
+
item = maker.items.new_item
|
29
|
+
item.title = "#{rev["msg"]} by #{rev["author"]}"
|
30
|
+
item.link = base_url+"/!svn/bc/#{rev["revision"]}"
|
31
|
+
item.date = Time.parse(rev["date"][0])
|
32
|
+
}
|
33
|
+
}
|
34
|
+
return @pipeline
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def base_url
|
40
|
+
return @config['target'].gsub(/\/$/, "")
|
41
|
+
end
|
42
|
+
|
43
|
+
def svn_log_argument
|
44
|
+
return [
|
45
|
+
base_url,
|
46
|
+
"--xml",
|
47
|
+
"--limit=#{limit}"
|
48
|
+
].join(" ")
|
49
|
+
end
|
50
|
+
|
51
|
+
def limit
|
52
|
+
return @config['fetch_items'] || 30
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Name:: Automatic::Plugin::Filter::Ignore
|
4
|
+
# Author:: 774 <http://id774.net>
|
5
|
+
# Created:: Feb 22, 2012
|
6
|
+
# Updated:: Mar 4, 2012
|
7
|
+
# Copyright:: 774 Copyright (c) 2012
|
8
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
|
+
|
10
|
+
module Automatic::Plugin
|
11
|
+
class FilterIgnore
|
12
|
+
def initialize(config, pipeline=[])
|
13
|
+
@config = config
|
14
|
+
@pipeline = pipeline
|
15
|
+
end
|
16
|
+
|
17
|
+
def exclude(items)
|
18
|
+
detection = false
|
19
|
+
unless @config['title'].nil?
|
20
|
+
@config['title'].each {|e|
|
21
|
+
if items.title.include?(e.chomp)
|
22
|
+
detection = true
|
23
|
+
Automatic::Log.puts("info", "Excluded by title: #{items.link}")
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
unless @config['link'].nil?
|
28
|
+
@config['link'].each {|e|
|
29
|
+
if items.link.include?(e.chomp)
|
30
|
+
detection = true
|
31
|
+
Automatic::Log.puts("info", "Excluded by link: #{items.link}")
|
32
|
+
end
|
33
|
+
}
|
34
|
+
end
|
35
|
+
unless @config['description'].nil?
|
36
|
+
@config['description'].each {|e|
|
37
|
+
if items.description.include?(e.chomp)
|
38
|
+
detection = true
|
39
|
+
Automatic::Log.puts("info", "Excluded by description: #{items.link}")
|
40
|
+
end
|
41
|
+
}
|
42
|
+
end
|
43
|
+
detection
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
return_feeds = []
|
48
|
+
@pipeline.each {|feeds|
|
49
|
+
ignore = false
|
50
|
+
unless feeds.nil?
|
51
|
+
feeds.items.each {|items|
|
52
|
+
ignore = true if exclude(items)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
return_feeds << feeds unless ignore
|
56
|
+
}
|
57
|
+
return_feeds
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# Name:: Automatic::Plugin::Filter::Image
|
4
|
+
# Author:: 774 <http://id774.net>
|
5
|
+
# Created:: Feb 28, 2012
|
6
|
+
# Updated:: Feb 28, 2012
|
7
|
+
# Copyright:: 774 Copyright (c) 2012
|
8
|
+
# License:: Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.0.
|
9
|
+
|
10
|
+
module Automatic::Plugin
|
11
|
+
class FilterImage
|
12
|
+
require 'net/http'
|
13
|
+
require 'kconv'
|
14
|
+
|
15
|
+
def initialize(config, pipeline=[])
|
16
|
+
@config = config
|
17
|
+
@pipeline = pipeline
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_array(string)
|
21
|
+
array = Array.new
|
22
|
+
string.scan(/<img src="(.*?)"/) { |matched|
|
23
|
+
array = array | matched
|
24
|
+
}
|
25
|
+
return array
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
return_feeds = []
|
30
|
+
@pipeline.each {|feeds|
|
31
|
+
img_url = ""
|
32
|
+
unless feeds.nil?
|
33
|
+
feeds.items.each {|feed|
|
34
|
+
arr = parse_array(feed.description)
|
35
|
+
if arr.length > 0
|
36
|
+
feed.link = arr[0]
|
37
|
+
else
|
38
|
+
feed.link = nil
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
return_feeds << feeds
|
43
|
+
}
|
44
|
+
return_feeds
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|