gregfitz23-chrono_trigger 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ == 0.0.2 2009-04-02
2
+
3
+ * 1 minor enhancement:
4
+ * Updated to add tasks file
5
+
6
+ == 0.0.1 2009-04-02
7
+
8
+ * 1 major enhancement:
9
+ * Initial release
@@ -0,0 +1,21 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/chrono_trigger.rb
7
+ lib/chrono_trigger/cron_entry.rb
8
+ lib/chrono_trigger/shell.rb
9
+ lib/chrono_trigger/tasks.rb
10
+ lib/chrono_trigger/trigger.rb
11
+ lib/triggers/test_triggers.rb
12
+ script/console
13
+ script/destroy
14
+ script/generate
15
+ tasks/chrono_trigger.rake
16
+ test/test_chrono_trigger.rb
17
+ test/test_cron_entry.rb
18
+ test/test_helper.rb
19
+ test/test_shell.rb
20
+ test/test_trigger.rb
21
+ test/triggers.rb
@@ -0,0 +1,3 @@
1
+ # Remember to add chrono_trigger.rake to lib/tasks in your rails project containing:
2
+ # require 'chrono_trigger/tasks'
3
+
@@ -0,0 +1,58 @@
1
+ = chrono_trigger
2
+
3
+ * http://github.com/gregfitz23/chrono_trigger/tree/master
4
+
5
+ == DESCRIPTION:
6
+
7
+ A cron framework for defining cron tasks using a readable DSL.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ == SYNOPSIS:
12
+
13
+ After installation, create lib/tasks/chrono_trigger.rake containing: require 'chrono_trigger/tasks'
14
+
15
+ Create trigger files (by default in the lib/triggers) directory.
16
+ Triggers should follow the pattern:
17
+
18
+ trigger "name" do
19
+ runs { code to execute }
20
+ on :monday
21
+ every :minutes=>10
22
+ at :hour=>9, :minute=>[30,50]
23
+ end
24
+
25
+ Execute rake RAILS_ENV={env} chrono_trigger:run
26
+
27
+ == REQUIREMENTS:
28
+
29
+ * ActiveSupport >= 2.0.2
30
+
31
+ == INSTALL:
32
+
33
+ * sudo gem install gregfitz23-chrono_trigger
34
+
35
+ == LICENSE:
36
+
37
+ (The MIT License)
38
+
39
+ Copyright (c) 2009 FIXME full name
40
+
41
+ Permission is hereby granted, free of charge, to any person obtaining
42
+ a copy of this software and associated documentation files (the
43
+ 'Software'), to deal in the Software without restriction, including
44
+ without limitation the rights to use, copy, modify, merge, publish,
45
+ distribute, sublicense, and/or sell copies of the Software, and to
46
+ permit persons to whom the Software is furnished to do so, subject to
47
+ the following conditions:
48
+
49
+ The above copyright notice and this permission notice shall be
50
+ included in all copies or substantial portions of the Software.
51
+
52
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
53
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
54
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
55
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
56
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
57
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
58
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 2
3
+ :major: 0
4
+ :minor: 0
@@ -0,0 +1,11 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module ChronoTrigger
5
+ VERSION = '0.0.2'
6
+ end
7
+
8
+ require "activesupport"
9
+ require "chrono_trigger/shell"
10
+ require "chrono_trigger/trigger"
11
+ require "chrono_trigger/cron_entry"
@@ -0,0 +1,54 @@
1
+ module ChronoTrigger
2
+
3
+ class CronEntry
4
+
5
+ def initialize(options={})
6
+ set_days(options[:days])
7
+ set_hours(options[:hours])
8
+ set_minutes(options[:minutes])
9
+ end
10
+
11
+ DAYS_CONVERSION = {
12
+ :sunday => 0,
13
+ :monday => 1,
14
+ :tuesday => 2,
15
+ :wednesday => 3,
16
+ :thursday => 4,
17
+ :friday => 5,
18
+ :saturday => 6
19
+ }
20
+
21
+ def set_hours(*args)
22
+ args.compact!
23
+ args.flatten!
24
+ raise ChronoTrigger::ConfigurationException.new("Hours must be less than 24") if args.any? {|hour| hour >= 24}
25
+ @hours = args
26
+ end
27
+
28
+ def set_days(*args)
29
+ args.compact!
30
+ args.flatten!
31
+ args.each {|day| raise ChronoTrigger::ConfigurationException.new("Day #{day} setting is invalid") if !DAYS_CONVERSION.keys.include?(day)}
32
+ @days = args.map { |day| DAYS_CONVERSION[day] }
33
+ end
34
+
35
+ def set_minutes(*args)
36
+ args.compact!
37
+ args.flatten!
38
+ raise ChronoTrigger::ConfigurationException.new("Minutes must be less than 60") if args.any? {|minute| minute >= 60}
39
+ @minutes = args
40
+ end
41
+
42
+ def matches?(datetime)
43
+ if @minutes.blank? && !@days.blank?
44
+ raise ChronoTrigger::ConfigurationException.new("Days were specified for a CronEntry with no minutes specified")
45
+ end
46
+
47
+ return false if !@minutes.blank? && !@minutes.include?(datetime.min)
48
+ return false if !@hours.blank? && !@hours.include?(datetime.hour)
49
+ return false if !@days.blank? && !@days.include?(datetime.wday)
50
+ return true
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ module ChronoTrigger
2
+
3
+ class ConfigurationException < Exception; end
4
+
5
+ class Shell
6
+
7
+ DEFAULT_TRIGGERS = "lib/triggers/*.rb"
8
+ #Load triggers defined in the trigger files by evaluating them in the context of this Shell instance.
9
+ def load_triggers(files = Dir.glob("#{DEFAULT_TRIGGERS}"))
10
+ files.each { |file| self.instance_eval(File.read(file), file) }
11
+ end
12
+
13
+ #Instantiate a trigger and evaluate the passed in block in the context of the trigger.
14
+ #This is the initial method call when setting up a configuration using the DSL.
15
+ def trigger(name, &block)
16
+ raise ConfigurationException.new("No configuration specified for trigger #{name}") unless block_given?
17
+
18
+ trigger = Trigger.new(name)
19
+ trigger.instance_eval(&block)
20
+
21
+ triggers << trigger
22
+ trigger
23
+ end
24
+
25
+ #Run execute on any trigger who's cron entry matches the current time.
26
+ def execute_triggers
27
+ now = Time.now
28
+ triggers.map {|trigger| trigger.execute_on_match(now)}
29
+ end
30
+
31
+ def triggers
32
+ @triggers ||= []
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.join(File.dirname(__FILE__), '..', '..', 'tasks', '*.rake')].each do |f|
2
+ load f
3
+ end
@@ -0,0 +1,92 @@
1
+ module ChronoTrigger
2
+ class Trigger
3
+
4
+ attr_accessor :name
5
+
6
+ def initialize(name)
7
+ self.name = name
8
+ end
9
+
10
+ #Define the code to be run when the cron job is ready to be executed.
11
+ def runs(&block)
12
+ @exec_block = block
13
+ end
14
+
15
+ #Specify what days the task should run on.
16
+ #Values are :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
17
+ def on(*days)
18
+ cron_entry.set_days(days)
19
+ end
20
+
21
+ #Specify what hours and minutes the task should run at
22
+ #(e.g. :minute=>10, :hour=>3 or :minute=>[10,20,30], :hour=>[1,2,3])
23
+ def at(options={})
24
+ validate_hours_or_minutes!(options)
25
+
26
+ if hour = options[:hour]
27
+ cron_entry.set_hours(hour)
28
+ end
29
+
30
+ if minute = options[:minute]
31
+ cron_entry.set_minutes(minute)
32
+ end
33
+ end
34
+
35
+ #Specify a repeating interval of hours and minutes to run.
36
+ #Specifying minutes not divisible by 60 result in an exception, use #at instead.
37
+ def every(options={})
38
+ validate_hours_or_minutes!(options)
39
+ if minutes = options[:minutes]
40
+ cron_entry.set_minutes(extract_minutes_for_every(minutes))
41
+ end
42
+
43
+ if hours = options[:hours]
44
+ cron_entry.set_hours(extract_hours_for_every(hours))
45
+ end
46
+ end
47
+
48
+ def dates
49
+ @dates ||= []
50
+ end
51
+
52
+ #Execute this Trigger's code block if the datetime param matches this Trigger's cron entry.
53
+ def execute_on_match(datetime)
54
+ self.execute if cron_entry.matches?(datetime)
55
+ end
56
+
57
+ def execute
58
+ @exec_block.call
59
+ end
60
+
61
+
62
+ private
63
+ def cron_entry
64
+ @cron_entry ||= CronEntry.new
65
+ end
66
+
67
+ #Raise an exception unless minutes and hours are set.
68
+ def validate_hours_or_minutes!(options={})
69
+ unless (options[:hours] || options[:hour]) || (options[:minutes] || options[:minute])
70
+ raise ChronoTrigger::ConfigurationException.new("Hours or minutes not specified in call to 'at' or 'every' method.")
71
+ end
72
+ end
73
+
74
+ def extract_minutes_for_every(minutes)
75
+ extract_for_every(minutes, 60)
76
+ end
77
+
78
+ def extract_hours_for_every(hours)
79
+ extract_for_every(hours, 24)
80
+ end
81
+
82
+ #Extract an array of integers representing the minutes or hours a task should be run at.
83
+ #Raise an exception if time_value is not evenly divisible by base.
84
+ def extract_for_every(time_value, base)
85
+ unless (base % time_value == 0)
86
+ raise ChronoTrigger::ConfigurationException.new("#{time_value} is not evenly divisible by #{base}. Consider using #at instead.")
87
+ end
88
+
89
+ (0...base).select {|num| num % time_value == 0}
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,26 @@
1
+ trigger "trigger1" do
2
+ runs { puts "trigger 1 runs every 1 minutes; executed at #{Time.now}"}
3
+ every :minutes=>1
4
+ end
5
+
6
+ trigger "trigger2" do
7
+ runs { puts "trigger 2 runs every 5 minutes; executed at #{Time.now}"}
8
+ every :minutes=>5
9
+ end
10
+
11
+ trigger "trigger3" do
12
+ runs { puts "trigger 3 runs at 9:48; executed at #{Time.now}"}
13
+ at :hour=>9, :minute=>48
14
+ end
15
+
16
+ trigger "trigger4" do
17
+ runs { puts "trigger 4 runs on monday at 9:53 and 9:56; executed at #{Time.now}"}
18
+ on :monday
19
+ at :hour=>9, :minute=>[53, 56]
20
+ end
21
+
22
+ trigger "trigger5" do
23
+ runs { puts "trigger 5 runs on thursday at 9:58/59; executed at #{Time.now}"}
24
+ on :thursday
25
+ at :hour=>9, :minute=>[58, 59]
26
+ end
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestChronoTrigger < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+ end
@@ -0,0 +1,136 @@
1
+ class TestCronEntry < Test::Unit::TestCase
2
+
3
+ class << self
4
+
5
+ def should_match(options={})
6
+ context "and a datetime of #{options.inspect}" do
7
+ setup do
8
+ @datetime = time_from_options(options)
9
+ end
10
+
11
+ should "return true on a call to matches?" do
12
+ assert @cron.matches?(@datetime)
13
+ end
14
+ end #and a datetime of options[]
15
+ end
16
+
17
+ def should_not_match(options={})
18
+ context "and a datetime of #{options.inspect}" do
19
+ setup do
20
+ @datetime = time_from_options(options)
21
+ end
22
+
23
+ should "return false on a call to matches?" do
24
+ assert !@cron.matches?(@datetime)
25
+ end
26
+ end #and a datetime of options[]
27
+ end
28
+
29
+ end
30
+
31
+ context "A CronEntry, @cron," do
32
+ setup do
33
+ @cron = ChronoTrigger::CronEntry.new
34
+ end
35
+
36
+ context "with a minutes entry of 10 minutes" do
37
+ setup do
38
+ @cron.set_minutes(10)
39
+ end
40
+
41
+ should_match(:minutes=>10)
42
+
43
+ should_not_match(:minutes=>11)
44
+
45
+ context "and 25 minutes" do
46
+ setup do
47
+ @cron.set_minutes(10, 25)
48
+ end
49
+
50
+ should_match(:minutes=>25)
51
+
52
+ should_match(:minutes=>10)
53
+
54
+ should_not_match(:minutes=>12)
55
+
56
+ context "and a day entry of monday" do
57
+ setup do
58
+ @cron.set_days(:monday)
59
+ end
60
+
61
+ should_match(:minutes=>10, :wday=>1)
62
+
63
+ should_not_match(:minutes=>10, :wday=>2)
64
+
65
+ context "and wednesday" do
66
+ setup do
67
+ @cron.set_days(:monday, :wednesday)
68
+ end
69
+
70
+ should_match(:minutes=>10, :wday=>1)
71
+
72
+ should_match(:minutes=>10, :wday=>3)
73
+
74
+ should_not_match(:minutes=>25, :wday=>5)
75
+
76
+ should_not_match(:minutes=>11, :wday=>3)
77
+
78
+ context "and an hour entry of 2" do
79
+ setup do
80
+ @cron.set_hours(5)
81
+ end
82
+
83
+ should_match(:minutes=>25, :wday=>3, :hour=>5)
84
+
85
+ should_not_match(:minutes=>25, :wday=>3, :hour=>6)
86
+ end #and an hour entry of 2
87
+ end #and wednesday
88
+ end #and a day entry of monday
89
+ end #and 25 minutes
90
+ end #with a minutes entry of 10 minutes
91
+
92
+ context "with a day entry of monday" do
93
+ setup do
94
+ @cron.set_days(:monday)
95
+ end
96
+
97
+ context "and no minutes_entry" do
98
+ setup do
99
+ @cron.set_minutes(nil)
100
+ end
101
+
102
+ should "raise a ChronoTrigger::CronEntry:ConfigException exception on matches?" do
103
+ assert_raise ChronoTrigger::ConfigurationException do
104
+ @cron.matches?(time_from_options)
105
+ end
106
+ end
107
+ end #and no minutes_entry
108
+ end #with a day entry
109
+
110
+ should "raise an exception when setting an hour entry greater than 25" do
111
+ assert_raise ChronoTrigger::ConfigurationException do
112
+ @cron.set_hours(25)
113
+ end
114
+ end
115
+ end #A CronEntry, @cron,
116
+
117
+
118
+ private
119
+ def time_from_options(options={})
120
+ datetime = Time.utc(options[:year] || 2000,
121
+ options[:month] || "jan",
122
+ options[:day]||1,
123
+ options[:hour]||0,
124
+ options[:minutes]||0,
125
+ options[:second]||0)
126
+
127
+ if wday = options[:wday]
128
+ while datetime.wday != wday
129
+ datetime += 1.day
130
+ end
131
+ end
132
+
133
+ datetime
134
+ end
135
+
136
+ end
@@ -0,0 +1,5 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/chrono_trigger'
4
+ require "shoulda"
5
+ require "mocha"
@@ -0,0 +1,52 @@
1
+ class TestShell < Test::Unit::TestCase
2
+
3
+ context "A shell instance, @shell" do
4
+ setup do
5
+ @shell = ChronoTrigger::Shell.new
6
+ end
7
+
8
+ should "return an empty array on call to triggers" do
9
+ assert_equal [], @shell.triggers
10
+ end
11
+
12
+ should "raise an exception unless a block is provided to #trigger" do
13
+ assert_raise(ChronoTrigger::ConfigurationException) { @shell.trigger("test trigger") }
14
+ end
15
+
16
+ context "when provided a valid block" do
17
+ setup do
18
+ @trigger = @shell.trigger("name") { runs { "x" } }
19
+ end
20
+
21
+ should "create a new trigger" do
22
+ assert_equal 1, @shell.triggers.size
23
+ end
24
+
25
+ should "create a trigger that will run x with execute" do
26
+ assert_equal "x", @shell.triggers.first.execute
27
+ end
28
+
29
+ context "and configured to run the next time shell.execute_triggers is called" do
30
+ setup do
31
+ ChronoTrigger::CronEntry.any_instance.stubs(:matches?).returns(true)
32
+ end
33
+
34
+ should "execute the code block" do
35
+ @trigger.expects(:execute)
36
+ @shell.execute_triggers
37
+ end
38
+ end #and configured to run the next time shell.execute_triggers is called
39
+ end #when provided a valid block
40
+
41
+ context "calling load_context with a valid trigger file" do
42
+ setup do
43
+ @shell.load_triggers("test/triggers.rb")
44
+ end
45
+
46
+ should "create 2 triggers" do
47
+ assert_equal 2, @shell.triggers.size
48
+ end
49
+ end #calling load_context with a valid trigger file
50
+ end #A shell instance, @shell
51
+
52
+ end
@@ -0,0 +1,116 @@
1
+ class TestTrigger < Test::Unit::TestCase
2
+
3
+ context "A Trigger, @trigger," do
4
+ setup do
5
+ @trigger = ChronoTrigger::Trigger.new("test trigger")
6
+ end
7
+
8
+ context "and a block of code to run" do
9
+ setup do
10
+ @block_to_run = "hello"
11
+ @trigger.runs { @block_to_run }
12
+ end
13
+
14
+ should "assign @exec_block on call to runs" do
15
+ assert_equal @block_to_run, @trigger.instance_variable_get(:@exec_block).call
16
+ end
17
+
18
+ should "execute @block_to_run on call to execute" do
19
+ assert_equal @block_to_run, @trigger.execute
20
+ end
21
+ end #and a block of code to run
22
+
23
+ context "and a call to #on with some days" do
24
+ setup do
25
+ @days = [:monday, :wednesday]
26
+ @expected_days = [1, 3]
27
+ @trigger.on(@days)
28
+ end
29
+
30
+ should "set the triggers cron entry days" do
31
+ assert_equal @expected_days, @trigger.instance_variable_get(:@cron_entry).instance_variable_get(:@days)
32
+ end
33
+ end #and a call to on with some days
34
+
35
+ context "and a call to #at with :hours and :minutes" do
36
+ setup do
37
+ @hours = 10
38
+ @minutes = 5
39
+ @trigger.at :hour=>@hours, :minute=>@minutes
40
+ end
41
+
42
+ should "set the trigger's cron entry's hours" do
43
+ assert_equal [@hours], @trigger.instance_variable_get(:@cron_entry).instance_variable_get(:@hours)
44
+ end
45
+
46
+ should "set the trigger's cron entry's minutes" do
47
+ assert_equal [@minutes], @trigger.instance_variable_get(:@cron_entry).instance_variable_get(:@minutes)
48
+ end
49
+
50
+ context "that are nil" do
51
+ setup do
52
+ @hours, @minutes = nil, nil
53
+ end
54
+
55
+ should "raise an exception" do
56
+ assert_raise ChronoTrigger::ConfigurationException do
57
+ @trigger.at :hours=>@hours, :minutes=>@minutes
58
+ end
59
+ end
60
+ end #that are nil
61
+ end #and a call to #at with :hours and :minutes
62
+
63
+
64
+ context "on a call to #every" do
65
+ context "for 10 minutes" do
66
+ setup do
67
+ @minutes = 10
68
+ end
69
+
70
+ context "when called" do
71
+ setup do
72
+ @trigger.every :minutes=>@minutes
73
+ end
74
+
75
+ should "set the trigger's cron entry's minutes to [0, 10,20,30,40,50]" do
76
+ assert_equal [0,10,20,30,40,50], @trigger.instance_variable_get(:@cron_entry).instance_variable_get(:@minutes)
77
+ end
78
+
79
+ context "in addition to setting #at to hour 2" do
80
+ setup do
81
+ @hour = 2
82
+ @trigger.at :hour=>@hour
83
+ end
84
+
85
+ should "set the trigger's cron entry's minutes to [0, 10,20,30,40,50]" do
86
+ assert_equal [0,10,20,30,40,50], @trigger.instance_variable_get(:@cron_entry).instance_variable_get(:@minutes)
87
+ end
88
+
89
+ should "set the trigger's cron entry's hours to 2" do
90
+ assert_equal [2], @trigger.instance_variable_get(:@cron_entry).instance_variable_get(:@hours)
91
+ end
92
+ end #in addition to setting #{}
93
+ end #when called
94
+ end #for 10 minutes
95
+
96
+ context "for 11 minutes" do
97
+ setup do
98
+ @minutes = 11
99
+ end
100
+
101
+ should "raise an exception" do
102
+ assert_raise ChronoTrigger::ConfigurationException do
103
+ @trigger.every :minutes=>@minutes
104
+ end
105
+ end
106
+ end #for 11 minutes
107
+ end #on a call to #every
108
+
109
+ should "raise an exception on call to #every without minutes or hours" do
110
+ assert_raise ChronoTrigger::ConfigurationException do
111
+ @trigger.every()
112
+ end
113
+ end
114
+ end #A Trigger, @trigger,
115
+
116
+ end
@@ -0,0 +1,11 @@
1
+ trigger "test_trigger" do
2
+ runs { "hello world" }
3
+ on :monday
4
+ at :hour=>3, :minute=>10
5
+ end
6
+
7
+ trigger "test_trigger_2" do
8
+ runs { "hello world 2" }
9
+ on :tuesday
10
+ every :hours=>2, :minutes=>10
11
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gregfitz23-chrono_trigger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Greg Fitzgerald
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-03 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: TODO
17
+ email: greg_fitz@yahoo.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - History.txt
26
+ - Manifest.txt
27
+ - PostInstall.txt
28
+ - README.rdoc
29
+ - VERSION.yml
30
+ - lib/chrono_trigger
31
+ - lib/chrono_trigger/cron_entry.rb
32
+ - lib/chrono_trigger/shell.rb
33
+ - lib/chrono_trigger/tasks.rb
34
+ - lib/chrono_trigger/trigger.rb
35
+ - lib/chrono_trigger.rb
36
+ - lib/triggers
37
+ - lib/triggers/test_triggers.rb
38
+ - test/test_chrono_trigger.rb
39
+ - test/test_cron_entry.rb
40
+ - test/test_helper.rb
41
+ - test/test_shell.rb
42
+ - test/test_trigger.rb
43
+ - test/triggers.rb
44
+ has_rdoc: true
45
+ homepage: ""
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --inline-source
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.2.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: TODO
71
+ test_files: []
72
+