schedule 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ A Ruby replacement for Cron
data/Rakefile ADDED
@@ -0,0 +1,115 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+ require "rake/testtask"
5
+
6
+ task :default => :test
7
+
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = "schedule"
10
+ s.version = "0.0.1"
11
+ s.summary = "A Ruby replacement for Cron"
12
+ s.author = "Mark Dodwell"
13
+ s.email = "hi@mkdynamic.co.uk"
14
+ s.homepage = "http://github.com/mkdynamic/schedule"
15
+
16
+ # Rdoc
17
+ # s.has_rdoc = true
18
+ # s.extra_rdoc_files = %w(README.md)
19
+ # s.rdoc_options = %w(--main README.md)
20
+
21
+ # Files
22
+ s.files = %w(Rakefile README.md) + Dir.glob("{bin,lib,test}/**/*")
23
+ s.executables = FileList["bin/**"].map { |f| File.basename(f) }
24
+ s.require_paths = ["bin", "lib"]
25
+
26
+ # Dependencies
27
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.3")
28
+ s.add_development_dependency("shoulda")
29
+ s.add_development_dependency("timecop")
30
+ end
31
+
32
+ #
33
+ # Specification
34
+ #
35
+
36
+ desc "Build the gemspec file #{spec.name}.gemspec"
37
+ task :gemspec do
38
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
39
+ File.open(file, "w") { |f| f.write(spec.to_ruby) }
40
+ end
41
+
42
+ #
43
+ # Build
44
+ #
45
+
46
+ Rake::GemPackageTask.new(spec) do |pkg|
47
+ pkg.gem_spec = spec
48
+ end
49
+
50
+ task :package => :gemspec # build gemspec when packaging
51
+
52
+ #
53
+ # Tests
54
+ #
55
+
56
+ Rake::TestTask.new do |t|
57
+ t.libs = [File.expand_path("lib"), "test"]
58
+ t.test_files = FileList["test/**/*_test.rb"]
59
+ t.verbose = true
60
+ end
61
+
62
+ #
63
+ # Documentation
64
+ #
65
+
66
+ # Rake::RDocTask.new do |rd|
67
+ # rd.main = "README.md"
68
+ # rd.rdoc_files.include("README.md", "lib/**/*.rb")
69
+ # rd.rdoc_dir = "rdoc"
70
+ # end
71
+
72
+ # desc "Clear out RDoc and generated packages"
73
+ # task :clean => [:clobber_rdoc, :clobber_package] do
74
+ # rm "#{spec.name}.gemspec"
75
+ # end
76
+
77
+ #
78
+ # Release
79
+ #
80
+
81
+ desc "Tag the repository in git with gem version number"
82
+ task :tag do
83
+ #`git fetch --tags`
84
+ if `git tag`.split("\n").include?("v#{spec.version}")
85
+ raise "Version #{spec.version} has already been released."
86
+ end
87
+
88
+ changed_files = `git diff --cached --name-only`.split("\n") + `git diff --name-only`.split("\n")
89
+ if changed_files == ["Rakefile"]
90
+ # MAGIC! automatically update Schedule::VERSION constant
91
+ file = "lib/#{spec.name}.rb"
92
+ s = File.read(file)
93
+ File.open(file, "w") do |f|
94
+ f.write(s.gsub(/^(\s*)VERSION(\s*)=(\s*)(["'])[\d\.]+(['"])(\s*)$/, %{\\1VERSION\\2=\\3\\4#{spec.version.to_s}\\5\\6}))
95
+ end
96
+ if File.read(file).match(/^\s*VERSION\s*=\s*"([\d\.]+)"\s*$/)[1] != spec.version.to_s
97
+ "VERSION constant in #{file} differs from gemspec version."
98
+ end
99
+
100
+ Rake::Task["package"].invoke
101
+
102
+ `git add #{File.expand_path("../#{spec.name}.gemspec", __FILE__)} Rakefile lib/schedule.rb`
103
+ `git commit -m "prepare version #{spec.version}"`
104
+ `git tag v#{spec.version}`
105
+ #`git push --tags`
106
+ #`git push`
107
+ else
108
+ raise "Repository contains uncommitted changes; either commit or stash."
109
+ end
110
+ end
111
+
112
+ # desc "Tag and publish the gem to rubygems.org"
113
+ # task :publish => :tag do
114
+ # `gem push pkg/#{spec.name}-#{spec.version}.gem`
115
+ # end
data/bin/cr ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + "/../lib")
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require "schedule/task"
7
+ require "schedule/runner"
8
+ require "schedule/notifier"
9
+ require "schedule/logger"
10
+
11
+ def invalid_arg!(arg, options, command)
12
+ raise "Invalid argument: #{options.inspect} / #{command.inspect} / #{arg.inspect}"
13
+ end
14
+
15
+ args = ARGV.dup
16
+ command = nil
17
+ options = {}
18
+ until args.empty? do
19
+ case (arg = args.shift.strip)
20
+ when /^[^\-]/
21
+ invalid_arg!(arg, options, command) unless command.nil?
22
+ command = arg
23
+ when "-n", "--name" then options[:name] = args.shift
24
+ when "-l", "--log" then options[:log] = args.shift
25
+ when "-e", "--email" then options[:email] = args.shift
26
+ when "--" # noop
27
+ else invalid_arg!(arg, options, command)
28
+ end
29
+ end
30
+
31
+ raise "Did not pass any command to run" unless command
32
+
33
+ task = Schedule::Task.new(options[:name], command)
34
+
35
+ logger = if options[:log]
36
+ Schedule::Logger.new(options[:log], options[:name])
37
+ else
38
+ nil
39
+ end
40
+
41
+ notifier = if options[:email]
42
+ Schedule::Notifier::Email.new({
43
+ :to => options[:email]
44
+ })
45
+ else
46
+ nil
47
+ end
48
+
49
+ exit Schedule::Runner.new(task, logger, notifier).run
data/lib/schedule.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "schedule/task"
2
+ require "schedule/notifier"
3
+ require "schedule/logger"
4
+ require "schedule/runner"
5
+
6
+ module Schedule
7
+ VERSION = "0.0.1"
8
+ end
@@ -0,0 +1,56 @@
1
+ require "thread"
2
+ require "stringio"
3
+
4
+ module Schedule
5
+ class Logger
6
+ LINE_SEPERATOR = /\r\n|\n|\r/ # CRLF, LF or CR
7
+
8
+ attr_reader :buffer
9
+
10
+ def initialize(device, prefix)
11
+ if device == STDOUT
12
+ @device = device
13
+ elsif device.is_a?(String)
14
+ @device = File.open(device, File::WRONLY | File::APPEND | File::CREAT)
15
+ @device.sync = true
16
+ else
17
+ raise ArgumentError, "Log device must be a file path or STDOUT"
18
+ end
19
+
20
+ @formatter = Proc.new { |line, timestamp| "#{timestamp.strftime("%Y-%m-%d %H:%M:%S")} [#{@prefix}] #{line}" }
21
+ @buffer = StringIO.new
22
+ @prefix = prefix
23
+ @semaphore = Mutex.new
24
+ end
25
+
26
+ def log(msg)
27
+ timestamp = Time.now
28
+
29
+ # split into lines, preserving whitespace
30
+ lines = msg.split(LINE_SEPERATOR)
31
+ msg.scan(LINE_SEPERATOR).size.times { lines << "\n" } if lines.empty?
32
+
33
+ unless lines.empty?
34
+ @semaphore.synchronize do
35
+ @device.flock(File::LOCK_EX) if device_is_file?
36
+ begin
37
+ @device.write(lines.map { |line| @formatter.call(line, timestamp) }.join("\n") + "\n")#
38
+ @buffer.write(lines.join("\n") + "\n")
39
+ ensure
40
+ @device.flock(File::LOCK_UN) if device_is_file?
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def close
47
+ @device.close
48
+ end
49
+
50
+ private
51
+
52
+ def device_is_file?
53
+ @device_is_file ||= @device.is_a?(File)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ require "schedule/notifier/base"
2
+ require "schedule/notifier/console"
3
+ require "schedule/notifier/email"
@@ -0,0 +1,21 @@
1
+ module Schedule
2
+ module Notifier
3
+ class Base
4
+ def initialize(opts = {})
5
+ @options = opts
6
+ end
7
+
8
+ def notify(task, message)
9
+ @task = task
10
+ @message = message
11
+ notify!
12
+ end
13
+
14
+ protected
15
+
16
+ def notify!
17
+ raise NotImplementedError, "You must subclass this method!"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ require "schedule/notifier/base"
2
+
3
+ module Schedule
4
+ module Notifier
5
+ class Console < Base
6
+ private
7
+
8
+ def notify!
9
+ mail = []
10
+ mail << "From: #{@options[:from]} <#{@options[:from]}>"
11
+ mail << "To: #{@options[:to]} <#{@options[:to]}>"
12
+ mail << "Subject: #{subject}"
13
+ mail << "\n#{@message}"
14
+ mail = mail.join("\n")
15
+
16
+ puts mail
17
+ end
18
+
19
+ def subject
20
+ "[Schedule] #{@task.name}"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ require "schedule/notifier/base"
2
+ require "net/smtp"
3
+ require "tempfile"
4
+
5
+ module Schedule
6
+ module Notifier
7
+ class Email < Base
8
+ private
9
+
10
+ def notify!
11
+ mail = []
12
+ mail << "From: #{@options[:from]} <#{@options[:from]}>" if @options[:from]
13
+ mail << "To: #{@options[:to]} <#{@options[:to]}>"
14
+ mail << "Subject: #{subject}"
15
+ mail << "\n#{@message}"
16
+ mail = mail.join("\n")
17
+
18
+ begin
19
+ Net::SMTP.start("localhost") do |smtp|
20
+ smtp.send_message(mail, @options[:from], @options[:to])
21
+ end
22
+ rescue
23
+ mail_file = Tempfile.new("mail")
24
+ mail_file.write(mail)
25
+ mail_file.close
26
+ begin
27
+ result = system("sendmail #{@options[:to]} < #{File.expand_path(mail_file.path)}")
28
+ raise unless result
29
+ rescue => e
30
+ raise "Could not deliver email using either local SMTP or sendmail!"
31
+ ensure
32
+ mail_file.unlink rescue nil
33
+ end
34
+ end
35
+ end
36
+
37
+ def subject
38
+ "[Schedule] #{@task.name}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,101 @@
1
+ require "schedule/logger"
2
+
3
+ module Schedule
4
+ class Runner
5
+ def initialize(task, logger = nil, notifier = nil)
6
+ @task = task
7
+ @logger = logger || Logger.new(STDOUT, @task.name)
8
+ @notifier = notifier
9
+ end
10
+
11
+ def run
12
+ @logger.log "=== Started (#{@task.command}) ==="
13
+
14
+ status = nil
15
+
16
+ duration = with_timing do
17
+ status = execute(@task.command) do |output|
18
+ buffer = ""
19
+ begin
20
+ while (buffer << output.readpartial(1024))
21
+ if lines = buffer.slice!(/^.*[\n\r]/m)
22
+ @logger.log(lines)
23
+ end
24
+ end
25
+ rescue EOFError
26
+ @logger.log(buffer) unless buffer == ""
27
+ ensure
28
+ buffer = nil
29
+ end
30
+ end
31
+ end
32
+
33
+ exit_status = if status && status.exitstatus == 0
34
+ 0
35
+ elsif status && status.exitstatus
36
+ status.exitstatus
37
+ else
38
+ 1
39
+ end
40
+
41
+ if exit_status == 0
42
+ @logger.log "=== Completed successfully (took #{duration} seconds) ==="
43
+ else
44
+ @logger.log "=== Failed (took #{duration} seconds) ==="
45
+ end
46
+
47
+ if @notifier
48
+ @notifier.notify(@task, @logger.buffer.string)
49
+ end
50
+
51
+ return exit_status
52
+ end
53
+
54
+ private
55
+
56
+ # executes system command as a child process (fork + exec)
57
+ # returns stdout and stderr as a single stream
58
+ # will execute code inside block
59
+ def execute(cmd)
60
+ status = nil
61
+
62
+ begin
63
+ pipe = IO::pipe
64
+
65
+ pid = fork do
66
+ pipe[0].close
67
+ STDOUT.reopen(pipe[1])
68
+ pipe[1].close
69
+
70
+ STDERR.reopen(STDOUT)
71
+
72
+ begin
73
+ exec cmd
74
+ rescue => e
75
+ # only some ruby level errors (e.g. exec('nonexistentfile')), caught here by this rescue
76
+ # once the process is exec'd it'll take us over so any errors at that
77
+ # level will be piped to STDERR/STDOUT and dealt with in out execute block in #run
78
+ STDERR.puts "Error running command: #{e.to_s} (#{e.class.name})"#\n#{e.backtrace.map { |l| " from #{l}" }.join("\n")}"
79
+ exit(1)
80
+ end
81
+ end
82
+
83
+ pipe[1].close
84
+
85
+ yield pipe[0]
86
+ ensure
87
+ _, status = Process.waitpid2(pid)# rescue nil
88
+ pipe.each { |io| io.close unless io.closed? }# rescue nil
89
+ end
90
+
91
+ status
92
+ end
93
+
94
+ def with_timing
95
+ started = Time.now
96
+ yield
97
+ finished = Time.now
98
+ finished - started
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,10 @@
1
+ module Schedule
2
+ class Task
3
+ attr_reader :name, :command
4
+
5
+ def initialize(name, command)
6
+ @name = name
7
+ @command = command
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ 11.times do |i|
4
+ $stdout.printf("\rProgress: %s", "#{i * 10}%")
5
+ $stdout.flush
6
+ sleep 0.25
7
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ `touch #{ARGV.first}`
4
+ puts "Running command..."
5
+ raise "Error!"
6
+ puts "done."
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ `touch #{ARGV.first}`
4
+ puts "Running command..."
5
+ puts "done."
@@ -0,0 +1,44 @@
1
+ require "test_helper"
2
+ require "tempfile"
3
+
4
+ class BinaryTest < Test::Unit::TestCase
5
+ context "command runner" do
6
+ context "successful command" do
7
+ setup do
8
+ @file = Tempfile.new("log")
9
+ @path = @file.path
10
+ @file.close
11
+ @file.unlink
12
+
13
+ command_to_run_path = File.expand_path("../../commands/success", __FILE__)
14
+ @command_to_run = "#{command_to_run_path} #{@path}"
15
+ @command_runner_path = File.expand_path("../../../bin/cr", __FILE__)
16
+ end
17
+
18
+ context "no options" do
19
+ setup do
20
+ @command = %{#{@command_runner_path} "#{@command_to_run}"}
21
+ end
22
+
23
+ should "execute command" do
24
+ silence_stream(STDOUT) do
25
+ system(@command)
26
+ assert File.exists?(@path)
27
+ end
28
+ end
29
+
30
+ should "return status code 0" do
31
+ silence_stream(STDOUT) do
32
+ assert_equal true, system(@command)
33
+ end
34
+ end
35
+
36
+ should "log to stdout"
37
+ end
38
+
39
+ teardown do
40
+ File.unlink(@path) rescue nil
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ require "rubygems"
2
+ require "test/unit"
3
+ require "shoulda"
4
+
5
+ class Test::Unit::TestCase
6
+ # from ActiveSupport 3
7
+ def silence_stream(stream)
8
+ old_stream = stream.dup
9
+ stream.reopen(RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ ? "NUL:" : "/dev/null")
10
+ stream.sync = true
11
+ yield
12
+ ensure
13
+ stream.reopen(old_stream)
14
+ end
15
+ end
@@ -0,0 +1,109 @@
1
+ require "test_helper"
2
+ require "schedule/logger"
3
+ require "tempfile"
4
+ require "timecop"
5
+
6
+ class LoggerTest < Test::Unit::TestCase
7
+ context "creating a new logger" do
8
+ context "with the path of an existing file" do
9
+ setup do
10
+ @file = Tempfile.new("log")
11
+ @path = @file.path
12
+
13
+ @file.write "Line 1\nLine2"
14
+ @file.close
15
+
16
+ @logger = Schedule::Logger.new(@path, "")
17
+ end
18
+
19
+ should "not overwrite the file" do
20
+ assert_equal "Line 1\nLine2", File.read(@path)
21
+ end
22
+
23
+ teardown do
24
+ @file.unlink
25
+ end
26
+ end
27
+
28
+ context "with the path of a non-existing file" do
29
+ setup do
30
+ file = Tempfile.new("log")
31
+ @path = file.path
32
+ file.close
33
+ file.unlink
34
+
35
+ @logger = Schedule::Logger.new(@path, "")
36
+ end
37
+
38
+ should "create the file" do
39
+ assert File.exists?(@path)
40
+ end
41
+ end
42
+
43
+ context "with stdout" do
44
+ should "not explode" do
45
+ assert_nothing_raised { Schedule::Logger.new(STDOUT, "") }
46
+ end
47
+ end
48
+ end
49
+
50
+ context "logging a message" do
51
+ setup do
52
+ file = Tempfile.new("log")
53
+ @path = file.path
54
+ file.close
55
+ file.unlink
56
+
57
+ @logger = Schedule::Logger.new(@path, "Awesome")
58
+ @now = Time.now
59
+ end
60
+
61
+ context "single line" do
62
+ setup do
63
+ Timecop.freeze(@now) do
64
+ @logger.log("Testing 123")
65
+ end
66
+ end
67
+
68
+ should "write to file immediately" do
69
+ assert_match "Testing 123", File.read(@path)
70
+ end
71
+
72
+ should "prefix message with time and label" do
73
+ assert_match /^.*#{@now.strftime("%Y-%m-%d %H:%M:%S")}.*Awesome.*Testing 123.*$/, File.read(@path)
74
+ end
75
+
76
+ context "logging a subsequent message" do
77
+ setup do
78
+ @logger.log("Entry number 2")
79
+ end
80
+
81
+ should "be written to a seperate line" do
82
+ lines = File.read(@path).split(/\n/)
83
+ assert_match "Testing 123", lines[0]
84
+ assert_match "Entry number 2", lines[1]
85
+ end
86
+ end
87
+ end
88
+
89
+ context "multiple lines" do
90
+ setup do
91
+ Timecop.freeze(@now) do
92
+ @logger.log("Multiline 1 \n\n Multiline 2\rMultiline 3 ")
93
+ end
94
+ end
95
+
96
+ should "write as multiple, prefixed lines, preserving per-line whitespace and blank lines" do
97
+ expecting = []
98
+ expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] Multiline 1 "
99
+ expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] "
100
+ expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] Multiline 2"
101
+ expecting << "#{@now.strftime("%Y-%m-%d %H:%M:%S")} [Awesome] Multiline 3 "
102
+ expecting << ""
103
+ expecting = expecting.join("\n")
104
+
105
+ assert_match expecting, File.read(@path)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,89 @@
1
+ require "test_helper"
2
+ require "schedule/task"
3
+ require "schedule/runner"
4
+ require "tempfile"
5
+
6
+ class RunnerTest < Test::Unit::TestCase
7
+ context "a successful task" do
8
+ setup do
9
+ @file = Tempfile.new("log")
10
+ @path = @file.path
11
+ @file.close
12
+
13
+ @task = Schedule::Task.new("Test", "echo `pwd` > #{@path}")
14
+ @runner = Schedule::Runner.new(@task)
15
+ end
16
+
17
+ should "return 0" do
18
+ silence_stream(STDOUT) do
19
+ assert_equal 0, @runner.run
20
+ end
21
+ end
22
+
23
+ should "execute command" do
24
+ silence_stream(STDOUT) do
25
+ @runner.run
26
+ assert_equal `pwd`, File.read(@path)
27
+ end
28
+ end
29
+
30
+ teardown do
31
+ @file.unlink
32
+ end
33
+ end
34
+
35
+ context "a non-existent command" do
36
+ setup do
37
+ @task = Schedule::Task.new("Test", "fsjfslsfdanl")
38
+ @runner = Schedule::Runner.new(@task)
39
+ end
40
+
41
+ should "return 1" do
42
+ silence_stream(STDOUT) do
43
+ assert_equal 1, @runner.run
44
+ end
45
+ end
46
+ end
47
+
48
+ context "a command which uses carriage returns to seperate lines" do
49
+ setup do
50
+ @task = Schedule::Task.new("Flushing", File.expand_path("../../commands/flushing", __FILE__))
51
+ @runner = Schedule::Runner.new(@task)
52
+ end
53
+
54
+ should "return 0" do
55
+ silence_stream(STDOUT) do
56
+ assert_equal 0, @runner.run
57
+ end
58
+ end
59
+ end
60
+
61
+ context "a command that raises an exception" do
62
+ setup do
63
+ @file = Tempfile.new("log")
64
+ @path = @file.path
65
+ @file.close
66
+ @file.unlink
67
+
68
+ @task = Schedule::Task.new("Test", File.expand_path("../../commands/raises_exception #{@path}", __FILE__))
69
+ @runner = Schedule::Runner.new(@task)
70
+ end
71
+
72
+ should "execute command" do#
73
+ silence_stream(STDOUT) do
74
+ @runner.run
75
+ assert File.exists?(@path)
76
+ end
77
+ end
78
+
79
+ should "return 1" do
80
+ silence_stream(STDOUT) do
81
+ assert_equal 1, @runner.run
82
+ end
83
+ end
84
+
85
+ teardown do
86
+ File.unlink(@path) rescue nil
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,18 @@
1
+ require "test_helper"
2
+ require "schedule/task"
3
+
4
+ class TaskTest < Test::Unit::TestCase
5
+ context "a new task" do
6
+ setup do
7
+ @task = Schedule::Task.new("Testing", "pwd")
8
+ end
9
+
10
+ should "return #name of Testing" do
11
+ assert_equal "Testing", @task.name
12
+ end
13
+
14
+ should "return #command of pwd" do
15
+ assert_equal "pwd", @task.command
16
+ end
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schedule
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Mark Dodwell
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-04-09 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: shoulda
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: timecop
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ description:
47
+ email: hi@mkdynamic.co.uk
48
+ executables:
49
+ - cr
50
+ extensions: []
51
+
52
+ extra_rdoc_files: []
53
+
54
+ files:
55
+ - Rakefile
56
+ - README.md
57
+ - bin/cr
58
+ - lib/schedule/logger.rb
59
+ - lib/schedule/notifier/base.rb
60
+ - lib/schedule/notifier/console.rb
61
+ - lib/schedule/notifier/email.rb
62
+ - lib/schedule/notifier.rb
63
+ - lib/schedule/runner.rb
64
+ - lib/schedule/task.rb
65
+ - lib/schedule.rb
66
+ - test/commands/flushing
67
+ - test/commands/raises_exception
68
+ - test/commands/success
69
+ - test/integration/binary_test.rb
70
+ - test/test_helper.rb
71
+ - test/unit/logger_test.rb
72
+ - test/unit/runner_test.rb
73
+ - test/unit/task_test.rb
74
+ has_rdoc: true
75
+ homepage: http://github.com/mkdynamic/schedule
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - bin
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 1
99
+ - 3
100
+ version: "1.3"
101
+ requirements: []
102
+
103
+ rubyforge_project:
104
+ rubygems_version: 1.3.7
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: A Ruby replacement for Cron
108
+ test_files: []
109
+