startat 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,6 @@
1
+ === 0.1.0 / 2009-07-29
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,8 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ examples/print-to-the-future.rb
6
+ examples/twitter-schedule-bot.rb
7
+ lib/startat.rb
8
+ test/test_startat.rb
@@ -0,0 +1,68 @@
1
+ = startat
2
+
3
+ * http://startat.rubyforge.org/
4
+
5
+ == DESCRIPTION:
6
+
7
+ StartAt is a simple class for future code execution. It is designed
8
+ to execute a block of code at a specific point in time in the future.
9
+
10
+ StartAt works by spawning a new thread, determining how long it must wait (in
11
+ seconds) until the future date and time is reached, calling sleep with the
12
+ exact number of seconds to wait, and then executing the code block.
13
+
14
+ StartAt was derived from a script written to post schedule information to
15
+ Twitter for a symposium. The schedule robot posted event details exactly
16
+ five minutes in advance of the event.
17
+
18
+ == FEATURES/PROBLEMS:
19
+
20
+ Issues:
21
+ * This is an alpha release. The developer is interested in feedback on the API.
22
+ * StartAt currently operates with whole seconds, not microseconds.
23
+ * A running (waiting) StartAt object can be interrupted early with SIGALARM.
24
+ * Thread safety issues have not been examined or addressed yet.
25
+
26
+ == SYNOPSIS:
27
+
28
+ require 'date'
29
+ require 'startat'
30
+
31
+ future_time = DateTime.parse("2010-03-24 03:34:45 PM EDT")
32
+ action = lambda { print "The time is now!" }
33
+ future_event = StartAt.new(future_time, &action)
34
+ future_event.start
35
+ future_event.join
36
+
37
+ == REQUIREMENTS:
38
+
39
+ * date
40
+
41
+ == INSTALL:
42
+
43
+ * sudo gem install startat
44
+
45
+ == LICENSE:
46
+
47
+ (The MIT License)
48
+
49
+ Copyright (c) 2009 Keith A. Watson <ikawnoclast@interocitry.com>
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of this software and associated documentation files (the
53
+ 'Software'), to deal in the Software without restriction, including
54
+ without limitation the rights to use, copy, modify, merge, publish,
55
+ distribute, sublicense, and/or sell copies of the Software, and to
56
+ permit persons to whom the Software is furnished to do so, subject to
57
+ the following conditions:
58
+
59
+ The above copyright notice and this permission notice shall be
60
+ included in all copies or substantial portions of the Software.
61
+
62
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
63
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
64
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
65
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
66
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
67
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
68
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec 'startat' do |p|
7
+ # self.rubyforge_name = 'startatx' # if different than 'startat'
8
+ p.developer('Keith Watson', 'ikawnoclast@interocitry.com')
9
+ p.remote_rdoc_dir = '' # Release to root
10
+ end
11
+
12
+ # vim: syntax=ruby
@@ -0,0 +1,70 @@
1
+ #! /usr/bin/env ruby -w
2
+
3
+ #
4
+ # print-to-the-future.rb -- Schedules and prints messages in the future.
5
+ #
6
+ # Copyright (c) 2009 Keith A. Watson <ikawnoclast@interocitry.com>
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # 'Software'), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to
13
+ # permit persons to whom the Software is furnished to do so, subject to
14
+ # the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be
17
+ # included in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
20
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+
28
+ require 'date'
29
+ require 'startat'
30
+
31
+ # The following is borrowed from the _Ruby Cookbook_, Lucas Carlson & Leonard
32
+ # Richardson, O'Reilly Press, page 111.
33
+ # Time.to_datetime exists -- it's private though.
34
+ class Time
35
+ def to_datetime
36
+ seconds = sec + Rational(usec, 10**6)
37
+ offset = Rational(utc_offset, 60 * 60 * 24)
38
+ DateTime.new(year, month, day, hour, min, seconds, offset)
39
+ end
40
+ end
41
+
42
+ Time_format = "%b %e %H:%M:%S"
43
+
44
+ puts "Start time: #{DateTime.now.strftime(Time_format)}"
45
+
46
+ schedule = {
47
+ (Time.now + 5).to_datetime => "5 seconds later",
48
+ (Time.now + 10).to_datetime => "10 seconds later",
49
+ (Time.now + 33).to_datetime => "33 seconds later",
50
+ (Time.now + 1 * 60).to_datetime => "1 minute later",
51
+ (Time.now + 2 * 60).to_datetime => "2 minutes later and done."
52
+ }
53
+
54
+ scheduled_events = Array.new
55
+
56
+ schedule.each { | event_time, msg |
57
+ action = lambda {
58
+ puts "#{DateTime.now.strftime(Time_format)} #{msg}"
59
+ }
60
+
61
+ # create and start the scheduler for the future event
62
+ future_event = StartAt.new(event_time, &action)
63
+ future_event.start
64
+
65
+ # store the event
66
+ scheduled_events.push(future_event)
67
+ }
68
+
69
+ # wait out the longest thread
70
+ sleep 2 * 60 + 1
@@ -0,0 +1,113 @@
1
+ #!/opt/local/bin/ruby -w
2
+
3
+ #
4
+ # twitter-schedule-bot.rb -- A code example for using StartAt for posting
5
+ # a schedule of events for an event to Twitter.
6
+ #
7
+ # Copyright (c) 2009 Keith A. Watson <ikawnoclast@interocitry.com>
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # 'Software'), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+
29
+ #
30
+ # NOTE: This is an updated version of the code CERIAS used to post the
31
+ # schedule of events for the 20009 CERIAS Information Security Symposium.
32
+ # This code is missing the Twitter account details (username and password)
33
+ # and the events have already elapsed. This is only an example, but is
34
+ # easily updated for your own events.
35
+ #
36
+
37
+ require 'rubygems'
38
+ require 'twitter'
39
+ require 'date'
40
+ require 'startat'
41
+
42
+ # message added to the beginning and end of the post
43
+ header="CERIAS Symposium Schedule:"
44
+ footer="#cerias"
45
+
46
+ # amount of time before the actual event to post info (in seconds)
47
+ leadTime = 5 * 60;
48
+
49
+ # Twitter account object creation
50
+ postService = Twitter::Base.new('myTwitterAccount', 'myTwitterPassword')
51
+
52
+ # schedule array containing event times and messages
53
+ schedule = {
54
+ DateTime.parse('2009-03-24 09:30:00 AM EDT') \
55
+ => "Registration and Coffee in LWSN Commons",
56
+ DateTime.parse('2009-03-24 10:30:00 AM EDT') \
57
+ => "Welcome in LWSN 1142",
58
+ DateTime.parse('2009-03-24 10:35:00 AM EDT') \
59
+ => "Panel #1: Transitive Security & Standards Adoption in LWSN 1142",
60
+ DateTime.parse('2009-03-24 12:15:00 PM EDT') \
61
+ => "Lunch in LWSN 1190",
62
+ DateTime.parse('2009-03-24 01:45:00 PM EDT') \
63
+ => "Panel #2: Security in the Cloud in LWSN 1142",
64
+ DateTime.parse('2009-03-24 03:30:00 PM EDT') \
65
+ => "Fireside Chat in LWSN 1142",
66
+ DateTime.parse('2009-03-24 07:00:00 PM EDT') \
67
+ => "John Thompson, CEO Symantec in STEW Fowler Hall",
68
+ DateTime.parse('2009-03-25 08:00:00 AM EDT') \
69
+ => "Registration and Coffee in LWSN Commons",
70
+ DateTime.parse('2009-03-25 08:30:00 AM EDT') \
71
+ => "Morning Keynote Address: Dr. Ronald Ritchey in LWSN 1142",
72
+ DateTime.parse('2009-03-25 09:45:00 AM EDT') \
73
+ => "Panel #3: Unsecured Economies in LWSN 1142",
74
+ DateTime.parse('2009-03-25 11:15:00 AM EDT') \
75
+ => "Tech Talk Poster Preview in LWSN 1142",
76
+ DateTime.parse('2009-03-25 12:30:00 PM EDT') \
77
+ => "Lunch and Awards in LWSN 1142",
78
+ DateTime.parse('2009-03-25 02:00:00 PM EDT') \
79
+ => "Poster Session & CERIAS Anniversary Cake in LWSN Commons",
80
+ DateTime.parse('2009-03-25 04:30:00 PM EDT') \
81
+ => "CERIAS Seminar in PFEN 241",
82
+ }
83
+
84
+ schedEvents = Array.new
85
+
86
+ schedule.keys.each { | event |
87
+ # cacluate the time to post the message before the actual event
88
+ postTime = StartAt.get_time_before(event, leadTime)
89
+ # create the action of posting a message; pasted to the scheduler
90
+ action = lambda {
91
+ msg = "#{header} #{schedule[event]} starts at #{event.strftime("%l:%M")} #{footer}"
92
+ puts "#{DateTime.now.strftime("%b %e %H:%M:%S")} Posting the following message: \"#{msg}\" (length=#{msg.length})"
93
+ puts postService.post(msg).inspect
94
+ }
95
+
96
+ # create and start the scheduler for the future event
97
+ futureEvent = StartAt.new(postTime, &action)
98
+ futureEvent.start
99
+
100
+ # store the event
101
+ schedEvents.push(futureEvent)
102
+ }
103
+
104
+ # find the longest running thread and join it
105
+ longestEvent = StartAt.new(DateTime.now)
106
+ schedEvents.each { | eventPostTime |
107
+ if eventPostTime > longestEvent
108
+ longestEvent = eventPostTime
109
+ end
110
+ }
111
+ longestEvent.join
112
+
113
+ exit 0
@@ -0,0 +1,121 @@
1
+ require 'date'
2
+
3
+ #
4
+ # StartAt is class for executing code at a specific point in the future.
5
+ # The object is initialized with a #DateTime object with a point of time in the
6
+ # future and a code block to be executed. Calling #start will calculate the
7
+ # wait time (in seconds), initialize a #Thread that will then +sleep+ for the
8
+ # appropriate amount of time, and then the code block will be executed.
9
+ #
10
+ # For example:
11
+ #
12
+ # require 'date'
13
+ # require 'startat'
14
+ #
15
+ # bday = DateTime.parse("2009-08-20 08:30:00 AM EDT")
16
+ # action = lambda { puts "Happy Birthday, Dad!" }
17
+ # wait_for_it = StartAt.new(bday, &action)
18
+ # wait_for_it.start
19
+ # wait_for_it.join
20
+ #
21
+ #
22
+ # StartAt was originally written to post schedule information for a symposium to
23
+ # Twitter (see the examples directory).
24
+ #
25
+ class StartAt
26
+ VERSION = '0.1.0'
27
+ include Comparable
28
+
29
+ Seconds_per_day = 24 * 60 * 60 # :nodoc:
30
+
31
+ attr_reader :start_datetime
32
+
33
+ # Returns a new future action object that will execute the code block
34
+ # at a point in the future as defined by #start_datetime.
35
+ def initialize(start_datetime, &block)
36
+ @thread_started = false
37
+ if start_datetime.class == DateTime
38
+ @start_datetime = start_datetime
39
+ else
40
+ raise ArgumentError.new("must have a DateTime object")
41
+ end
42
+ if block
43
+ @action = block
44
+ elsif block_given?
45
+ @action = lambda { yield }
46
+ else
47
+ raise ArgumentError.new("missing a block")
48
+ end
49
+ end
50
+
51
+ # Compares the start #DateTime of the current future action with the
52
+ # start time of the #other future action.
53
+ def <=> (other)
54
+ @start_datetime <=> other.start_datetime
55
+ end
56
+
57
+ # Starts the future action thread by determining the time needed to wait
58
+ # based on the start date and then executes the code block once the time
59
+ # has elapsed.
60
+ def start
61
+ if ! @thread_started
62
+ @sleeper = Thread.new do
63
+ wait = get_wait_time()
64
+ # Check that the wait time is positive, otherwise the future
65
+ # event time has already past. We skip it, since a warning
66
+ # message was produced in wait_time.
67
+ if wait > 0
68
+ sleep wait
69
+ @action.call
70
+ end
71
+ end
72
+ @thread_started = true
73
+ end
74
+ end
75
+
76
+ # Cancels a running future action object thread.
77
+ def cancel
78
+ if ! @thread_started
79
+ @sleeper.exit
80
+ @thread_started = false
81
+ end
82
+ end
83
+
84
+ alias kill cancel
85
+
86
+ # Returns the status of a future action object thread. See #Thread#status
87
+ def status
88
+ @sleeper.status
89
+ end
90
+
91
+ # Joins the current thread to the future action thread. See #Thread.join
92
+ def join
93
+ @sleeper.join
94
+ end
95
+
96
+ # Takes a future #DateTime object and returns a #DateTime object for the time
97
+ # before as specified in +time_before+ in seconds.
98
+ def self.get_time_before(future_datetime, time_before)
99
+ future_datetime - Rational(time_before, Seconds_per_day)
100
+ end
101
+
102
+ # Takes a future #DateTime object and returns a #DateTime object for the time
103
+ # after as specified in +time_before+ in seconds.
104
+ def self.get_time_after(future_datetime, time_after)
105
+ future_datetime + Rational(time_after, Seconds_per_day)
106
+ end
107
+
108
+ private
109
+
110
+ def get_wait_time
111
+ # 1. Find the difference between the time now and the future time.
112
+ # 2. DateTime objects store time in whole seconds, so convert to the
113
+ # number of seconds in a day and return an interger.
114
+ wait_time = (((@start_datetime - DateTime.now) * Seconds_per_day) + 1).to_i
115
+ # send a warning message if the wait time is negative since the future
116
+ # time is no in the past.
117
+ warn "Specified date and time of #{@start_datetime.to_s} has already elapsed" if wait_time <= 0
118
+ return wait_time
119
+ end
120
+
121
+ end
@@ -0,0 +1,142 @@
1
+ require "test/unit"
2
+ require "date"
3
+ require "startat"
4
+
5
+ # The following is borrowed from the _Ruby Cookbook_, Lucas Carlson & Leonard
6
+ # Richardson, O'Reilly Press, page 111.
7
+ # Time.to_datetime exists -- it's private though.
8
+ class Time
9
+ def to_datetime
10
+ seconds = sec + Rational(usec, 10**6)
11
+ offset = Rational(utc_offset, 60 * 60 * 24)
12
+ DateTime.new(year, month, day, hour, min, seconds, offset)
13
+ end
14
+ end
15
+
16
+ # Unit test suite
17
+ class TestStartat < Test::Unit::TestCase
18
+ def test_initialization1
19
+ action = lambda { val = true }
20
+ test = nil
21
+ start = DateTime.now
22
+ assert_nothing_raised(ArgumentError) {
23
+ test = StartAt.new(start, &action)
24
+ }
25
+ assert_not_nil test
26
+ assert_equal(start, test.start_datetime)
27
+ end
28
+
29
+ def test_initialization2
30
+ test = nil
31
+ start = DateTime.now
32
+ assert_nothing_raised(ArgumentError) {
33
+ test = StartAt.new(start) { val = true }
34
+ }
35
+ assert_not_nil test
36
+ assert_same(start, test.start_datetime)
37
+ end
38
+
39
+ def test_initialization3
40
+ test = StartAt
41
+ assert_raise(ArgumentError) {
42
+ test.new(DateTime.now)
43
+ }
44
+ end
45
+
46
+ def test_initialization4
47
+ assert_raise(ArgumentError) {
48
+ test = StartAt.new(Time.now) { val = true }
49
+ }
50
+ end
51
+
52
+ def test_initialization5
53
+ assert_raise(ArgumentError) {
54
+ test = StartAt.new("This should fail") { val = true }
55
+ }
56
+ end
57
+
58
+ def test_start1
59
+ action = lambda { val = true }
60
+ test = StartAt.new((Time.now + 5).to_datetime, &action)
61
+ test.start
62
+ assert_equal("sleep", test.status)
63
+ test.cancel
64
+ end
65
+
66
+ def test_stop1
67
+ action = lambda { val = true }
68
+ test = StartAt.new((Time.now + 5).to_datetime, &action)
69
+ test.start
70
+ test.cancel
71
+ assert(test.status)
72
+ end
73
+
74
+ def test_stop2
75
+ action = lambda { val = true }
76
+ test = StartAt.new((Time.now + 5).to_datetime, &action)
77
+ test.start
78
+ test.kill
79
+ assert(test.status)
80
+ end
81
+
82
+ def comparison1
83
+ test1 = StartAt
84
+ test2 = StartAt
85
+ test1.new((Time.now + 2).to_datetime) { val1 = true }
86
+ test2.new((Time.now + 3).to_datetime) { val2 = true }
87
+ assert_not_equal(test1, test2)
88
+ end
89
+
90
+ def comparison2
91
+ date_time = (Time.now + 2).to_datetime
92
+ test1 = StartAt.new(date_time) { val1 = true }
93
+ test2 = StartAt.new(date_time) { val2 = true }
94
+ assert_equal(test1, test2)
95
+ end
96
+
97
+ def comparison3
98
+ test1 = StartAt
99
+ test2 = StartAt
100
+ test1.new((Time.now + 2).to_datetime) { val1 = true }
101
+ test2.new((Time.now + 3).to_datetime) { val2 = true }
102
+ assert_operator(test1, :<, test2)
103
+ end
104
+
105
+ def test_invalid_time1
106
+ action = lambda { val = true }
107
+ test = StartAt.new((Time.now - 2).to_datetime, &action)
108
+ test.start
109
+ assert(!test.status)
110
+ end
111
+
112
+ def test_timing1
113
+ val = true
114
+ test = StartAt.new((Time.now + 2).to_datetime) { val = false }
115
+ test.start
116
+ assert(val)
117
+ sleep 1
118
+ assert(val)
119
+ sleep 2
120
+ assert(!val)
121
+ end
122
+
123
+ def test_get_time_before1
124
+ time_now = Time.now
125
+ datetime_now = time_now.to_datetime
126
+ lead_times = [ 5, 30, 120, 3600, 43200, 86400, 604800 ]
127
+ lead_times.each { | lead_time |
128
+ time_before = StartAt.get_time_before(datetime_now, lead_time)
129
+ assert_equal((time_now - lead_time).to_datetime, time_before)
130
+ }
131
+ end
132
+
133
+ def test_get_time_after1
134
+ time_now = Time.now
135
+ datetime_now = time_now.to_datetime
136
+ lead_times = [ 5, 30, 120, 3600, 43200, 86400, 604800 ]
137
+ lead_times.each { | lead_time |
138
+ time_before = StartAt.get_time_after(datetime_now, lead_time)
139
+ assert_equal((time_now + lead_time).to_datetime, time_before)
140
+ }
141
+ end
142
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: startat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Keith Watson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDRDCCAiygAwIBAgIBADANBgkqhkiG9w0BAQUFADBIMRQwEgYDVQQDDAtpa2F3
14
+ bm9jbGFzdDEbMBkGCgmSJomT8ixkARkWC2ludGVyb2NpdHJ5MRMwEQYKCZImiZPy
15
+ LGQBGRYDY29tMB4XDTA5MDcyOTEzNTcxOFoXDTEwMDcyOTEzNTcxOFowSDEUMBIG
16
+ A1UEAwwLaWthd25vY2xhc3QxGzAZBgoJkiaJk/IsZAEZFgtpbnRlcm9jaXRyeTET
17
+ MBEGCgmSJomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
18
+ ggEBAM6Pn7BiDNnNRWUQzmuPBr3vNgp9TIB1SrJ3a9c1iJY6sYaTZD4M+JUmMpfo
19
+ FzQGdAAASOY0aJpBaZdA13fEC7fCed7pr44sf5mxZv1cNsUrXJbw9ZNBQFHyouz6
20
+ YTvO7pd24F2/DmxngutwlgCZlXwvkB5mrEfNbqIof3UhYkvK7mHBEJ5AWlGQ1nft
21
+ K0x72nTi3ZIg0mufz2kMadWcwK8I7A74FuGdFbMcLmoXJb4DwxCKb3APnS8UzoYV
22
+ /y5ddiogWbSEi+coOY0t6BkkS0kAEPf1tW6UtWB8yI+/807kyP/BQ3sQ2IoBAi04
23
+ +khoWTEyqkYzWmH2zRYSuh5U7RMCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8E
24
+ BAMCBLAwHQYDVR0OBBYEFMSjxPX6jYn3js6kC8BqsQH1c3+VMA0GCSqGSIb3DQEB
25
+ BQUAA4IBAQCE9cS2qD2MGQ4mWHIQOeJk9y7mQle7L25Z3tuMVNG0tPwmnZlaXpeE
26
+ DSYDKVCCoJQCH6m0H5KX7Jns0Y3/4unzQzfGVEh1JNojay5f2sV4YwRO+33/Ymxd
27
+ hhqmbytCY5ie36XoOC0aayVulcqMaHfgUVAUPN3bBudPANpVdF79yqBhjuNuCl3R
28
+ AwTBaYaGwOoSCGMUpOh0F4ADlqFKlZZMlxr2WXQJD2cqfMnLuzKgoL5V/Hkes8OL
29
+ Wh0bTgBFH0mpCwLpypJgZiuM6xhz90YTH7WzLSz9itIx2Vhrvylvxq4ePPfuefPX
30
+ Ia165vMqyt5W7zBTANDsPWi6m1DiaDTD
31
+ -----END CERTIFICATE-----
32
+
33
+ date: 2009-07-29 00:00:00 -04:00
34
+ default_executable:
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: hoe
38
+ type: :development
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 2.3.2
45
+ version:
46
+ description: |-
47
+ StartAt is a simple class for future code execution. It is designed
48
+ to execute a block of code at a specific point in time in the future.
49
+
50
+ StartAt works by spawning a new thread, determining how long it must wait (in
51
+ seconds) until the future date and time is reached, calling sleep with the
52
+ exact number of seconds to wait, and then executing the code block.
53
+
54
+ StartAt was derived from a script written to post schedule information to
55
+ Twitter for a symposium. The schedule robot posted event details exactly
56
+ five minutes in advance of the event.
57
+ email:
58
+ - ikawnoclast@interocitry.com
59
+ executables: []
60
+
61
+ extensions: []
62
+
63
+ extra_rdoc_files:
64
+ - History.txt
65
+ - Manifest.txt
66
+ - README.txt
67
+ files:
68
+ - History.txt
69
+ - Manifest.txt
70
+ - README.txt
71
+ - Rakefile
72
+ - examples/print-to-the-future.rb
73
+ - examples/twitter-schedule-bot.rb
74
+ - lib/startat.rb
75
+ - test/test_startat.rb
76
+ has_rdoc: true
77
+ homepage: http://startat.rubyforge.org/
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --main
83
+ - README.txt
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ version:
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: "0"
97
+ version:
98
+ requirements: []
99
+
100
+ rubyforge_project: startat
101
+ rubygems_version: 1.3.4
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: StartAt is a simple class for future code execution
105
+ test_files:
106
+ - test/test_startat.rb
@@ -0,0 +1,3 @@
1
+ bʼw�!>[�7���
2
+ r���Sb��m)C��?N��$�- �L��%�T6(SVU��[wy���f�v��
3
+ �W0���b ���X�ʟ�H@@^�⠍�����d�%䀫q����sB-@����?t������ۇ�S��!*���m�h� gb�:�"3��x�*���Lc��P��ي�'\��x5ʹC���6����0�� 3 �uz}������6X�3�ܫ���]��� D��,��vDN{B�サ�0�X�q�