thunk 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.autotest ADDED
@@ -0,0 +1,5 @@
1
+ require "autotest/restart"
2
+
3
+ Autotest.add_hook :initialize do |at|
4
+ at.testlib = "minitest/autorun"
5
+ end
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,4 @@
1
+ === 0.0.0 / 2010-01-25
2
+
3
+ * First prerelease. Pretty darn rough.
4
+
data/Manifest.txt ADDED
@@ -0,0 +1,8 @@
1
+ .autotest
2
+ CHANGELOG.rdoc
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ bin/thunk
7
+ lib/thunk.rb
8
+ test/test_thunk.rb
data/README.rdoc ADDED
@@ -0,0 +1,72 @@
1
+ = Thunk!
2
+
3
+ * http://github.com/eleven/thunk
4
+ * http://thunk.us
5
+
6
+ == Description
7
+
8
+ An API for talking to thunk.us, a (soon to be) generally awesome
9
+ notification service. Thunk lets you share and change simple state.
10
+
11
+ We know these docs are rough, but we wanted to get a version of the
12
+ API out quickly. We'll have it all polished by 1.0, we promise. :)
13
+
14
+ Since Thunk gets used in basic infrastructure tools, it works extra
15
+ hard to never throw exceptions or cause hangs/timeouts. If it does,
16
+ please file a bug! Even a nonexistent or read-only thunk will
17
+ successfully respond to pokes, returning stub information. If you need
18
+ to make extra sure you're hooked up to a valid thunk, check out
19
+ <tt>Thunk#exists?</tt> and <tt>Thunk#pokeable?</tt>.
20
+
21
+ == Examples
22
+
23
+ (Eventually) check out the RDoc for more detailed information.
24
+
25
+ === Poking & Reading a Thunk
26
+
27
+ thunk = Thunk.for "0681851956dccf88"
28
+ thunk.good! "Everything went better than expected!"
29
+
30
+ thunk.good? # => true
31
+ thunk.payload # => "Everything went..."
32
+
33
+ === Creating a Thunk
34
+
35
+ thunk = Thunk.create "My New Thunk"
36
+
37
+ thunk.name # => "My New Thunk"
38
+ thunk.state # => :new
39
+ thunk.uuid # => "..."
40
+
41
+ thunk.good! "I just made this thunk! Aaugh!"
42
+
43
+ === At the Command Line
44
+
45
+ $ thunk -h
46
+
47
+ == Installation
48
+
49
+ $ gem install thunk
50
+
51
+ == License
52
+
53
+ Copyright 2010 Eleven Eleven (hello@thunk.us)
54
+
55
+ Permission is hereby granted, free of charge, to any person obtaining
56
+ a copy of this software and associated documentation files (the
57
+ 'Software'), to deal in the Software without restriction, including
58
+ without limitation the rights to use, copy, modify, merge, publish,
59
+ distribute, sublicense, and/or sell copies of the Software, and to
60
+ permit persons to whom the Software is furnished to do so, subject to
61
+ the following conditions:
62
+
63
+ The above copyright notice and this permission notice shall be
64
+ included in all copies or substantial portions of the Software.
65
+
66
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
67
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
68
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
69
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
70
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
71
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
72
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "hoe"
2
+
3
+ Hoe.plugin :doofus, :git
4
+ Hoe.plugins.delete :rubyforge
5
+
6
+ Hoe.spec "thunk" do
7
+ developer "Eleven Eleven", "hello@3lev3n.com"
8
+
9
+ self.extra_rdoc_files = Dir["*.rdoc"]
10
+ self.history_file = "CHANGELOG.rdoc"
11
+ self.readme_file = "README.rdoc"
12
+ self.testlib = :minitest
13
+
14
+ extra_deps << %w(json_pure 1.2.0)
15
+ extra_dev_deps << %w(fakeweb 1.2.8)
16
+ extra_dev_deps << %w(minitest 1.5.0)
17
+ end
data/bin/thunk ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "thunk"
5
+
6
+ OptionParser.new do |opts|
7
+ @payload = nil
8
+ @poke = nil
9
+ @raw = false
10
+
11
+ opts.banner =
12
+ "Usage: thunk [options] --create [message]\n" +
13
+ " thunk [options] <uuid>\n" +
14
+ " thunk [options] {--good, --bad, --iffy, --unknown} <uuid>\n\n"
15
+
16
+ opts.on "--create [PAYLOAD]", "-c", "Create a thunk, optional payload." do |p|
17
+ thunk = Thunk.create p
18
+ puts thunk.uuid if thunk.uuid
19
+ exit thunk.exists? ? 0 : 1
20
+ end
21
+
22
+ opts.on "--help", "-h", "-?", "Show this help." do
23
+ puts opts
24
+ exit 0
25
+ end
26
+
27
+ opts.on "--payload PAYLOAD", "-p", "-m",
28
+ "An optional poke payload/message." do |p|
29
+
30
+ @payload = p
31
+ end
32
+
33
+ opts.on "--raw", "Show the thunk data as raw JSON." do
34
+ @raw = true
35
+ end
36
+
37
+ opts.on "--url URL", "Use a different base URL." do |url|
38
+ Thunk.url = url
39
+ end
40
+
41
+ opts.on "--version", "-V", "Prints #{Thunk::VERSION}." do
42
+ puts Thunk::VERSION
43
+ exit 0
44
+ end
45
+
46
+ %w(good iffy bad unknown).each do |state|
47
+ opts.on "--#{state}", "-#{state[0,1]}", "Send a '#{state}' poke." do
48
+ @poke = state
49
+ end
50
+ end
51
+
52
+ if ARGV.empty?
53
+ puts opts
54
+ exit 1
55
+ end
56
+
57
+ begin
58
+ opts.parse! ARGV
59
+ rescue OptionParser::ParseError => e
60
+ abort "#$0: #{e.message}"
61
+ end
62
+
63
+ abort "#$0: Specify a thunk UUID!" unless uuid = ARGV.shift
64
+ thunk = Thunk.for uuid
65
+
66
+ if @poke
67
+ p @payload
68
+ thunk.send "#{@poke}!", @payload
69
+ else
70
+ if @raw
71
+ puts thunk.json
72
+ else
73
+ str = [thunk.name, thunk.state].compact.join ", "
74
+ str << ", updated at #{thunk.updated}" if thunk.updated
75
+ str << ": #{thunk.payload}" if thunk.payload
76
+
77
+ puts str
78
+ end
79
+
80
+ exit({ :new => 0, :good => 0, :iffy => 2,
81
+ :bad => 4, :iffy => 8, :unknown => 16 }[thunk.state])
82
+ end
83
+ end
data/lib/thunk.rb ADDED
@@ -0,0 +1,160 @@
1
+ require "cgi"
2
+ require "json/pure"
3
+ require "net/http"
4
+ require "uri"
5
+
6
+ class Thunk
7
+ VERSION = "0.0.0"
8
+ URL = "http://thunk.us".freeze
9
+
10
+ class << self
11
+ attr_accessor :url
12
+ private :new
13
+
14
+ def create name = nil
15
+ json = request :post, url, :name => name
16
+ new "#{url}/#{json["uuid"]}", json
17
+ end
18
+
19
+ def for url
20
+ url = "#{self.url}/#{url}" unless /^http/ =~ url
21
+ json = request :get, url
22
+ new url, json
23
+ end
24
+
25
+ def request method, url, options = {}
26
+ url = URI.parse url
27
+
28
+ unless options.empty?
29
+ url.query = options.collect { |k ,v|
30
+ "#{CGI.escape k.to_s}=#{CGI.escape v.to_s}"
31
+ }.join "&"
32
+ end
33
+
34
+ # FIX http timeout settings
35
+ # FIX exception handing, etc
36
+
37
+ http = Net::HTTP.new url.host, url.port
38
+ rclass = { :get => Net::HTTP::Get, :post => Net::HTTP::Post }[method]
39
+ req = rclass.new url.request_uri
40
+ res = http.request req
41
+ success = %w(200 201).include? res.code
42
+
43
+ unless success
44
+ warn "[Thunk] #{res.code}, #{method}, #{url}: #{res.message}"
45
+ warn res.body if res.body
46
+ end
47
+
48
+ return success if res.body.empty?
49
+
50
+ data = JSON.parse(res.body) rescue {}
51
+
52
+ unless Hash === data
53
+ warn "[Thunk] Malformed JSON response: #{data.inspect}"
54
+ data = {}
55
+ end
56
+
57
+ warn warning if warning = data.delete("warn")
58
+ abort critical if critical = data.delete("abort")
59
+
60
+ data
61
+ end
62
+ end
63
+
64
+ self.url = URL
65
+
66
+ attr_reader :name
67
+ attr_reader :payload
68
+ attr_reader :puuid
69
+ attr_reader :state
70
+ attr_reader :updated
71
+ attr_reader :url
72
+ attr_reader :uuid
73
+
74
+ def bad?
75
+ :bad == state
76
+ end
77
+
78
+ def bad! payload = nil
79
+ poke :bad, payload
80
+ end
81
+
82
+ def exists?
83
+ self.class.request :get, url
84
+ end
85
+
86
+ def good?
87
+ :good == state
88
+ end
89
+
90
+ def good! payload = nil
91
+ poke :good, payload
92
+ end
93
+
94
+ def iffy?
95
+ :iffy == state
96
+ end
97
+
98
+ def iffy! payload = nil
99
+ poke :iffy, payload
100
+ end
101
+
102
+ def json
103
+ json = {
104
+ :name => name,
105
+ :payload => payload,
106
+ :puuid => puuid,
107
+ :state => state,
108
+ :updated => updated,
109
+ :url => url,
110
+ :uuid => uuid
111
+ }
112
+
113
+ json.reject! { |k, v| v.nil? }
114
+ JSON.generate json
115
+ end
116
+
117
+ def poke state, payload = nil
118
+ if self.class.request :post, "#{url}/#{state}", :payload => payload
119
+ @payload = payload
120
+ @state = state.to_sym
121
+ @updated = Time.now.utc
122
+ end
123
+
124
+ @state
125
+ end
126
+
127
+ def pokeable?
128
+ puuid
129
+ end
130
+
131
+ def public?
132
+ !puuid
133
+ end
134
+
135
+ def unknown?
136
+ :unknown == state
137
+ end
138
+
139
+ def unknown! payload = nil
140
+ poke :unknown, payload
141
+ end
142
+
143
+ private
144
+
145
+ def initialize url, json
146
+ @url = url
147
+ apply json
148
+ end
149
+
150
+ def apply json
151
+ @name = json["name"]
152
+ @payload = json["payload"]
153
+ @state = (json["state"] || "unknown").to_sym
154
+ @updated = json["updated"]
155
+ @uuid = json["uuid"]
156
+ @puuid = json["puuid"]
157
+
158
+ self
159
+ end
160
+ end
@@ -0,0 +1,120 @@
1
+ require "fakeweb"
2
+ require "minitest/autorun"
3
+
4
+ require "thunk"
5
+
6
+ FakeWeb.allow_net_connect = false
7
+
8
+ class Thunk
9
+ class << self
10
+ def abort *; end
11
+ def warn *; end
12
+
13
+ public :new
14
+ end
15
+
16
+ public :initialize
17
+ end
18
+
19
+ class TestThunk < MiniTest::Unit::TestCase
20
+ def setup
21
+ FakeWeb.clean_registry
22
+ Thunk.url = Thunk::URL
23
+ end
24
+
25
+ def test_bangs_and_predicates
26
+ t = Thunk.new "http://thunk.us/uuid", {}
27
+
28
+ [:good, :iffy, :bad, :unknown].each do |state|
29
+ FakeWeb.register_uri :post, "http://thunk.us/uuid/#{state}?payload=hi",
30
+ :status => 201
31
+
32
+ t.send "#{state}!", "hi"
33
+ assert t.send("#{state}?"), "thunk is #{state}"
34
+
35
+ refute_nil t.updated
36
+ assert_equal "hi", t.payload
37
+ end
38
+ end
39
+
40
+ def test_exists?
41
+ good = Thunk.new "http://thunk.us/exists", {}
42
+ bad = Thunk.new "http://thunk.us/nonexistent", {}
43
+
44
+ FakeWeb.register_uri :get, "http://thunk.us/exists", :status => 200
45
+ FakeWeb.register_uri :get, "http://thunk.us/nonexistent", :status => 404
46
+
47
+ assert good.exists?, "good exists"
48
+ refute bad.exists?, "nonexistent"
49
+ end
50
+
51
+ def test_pokeable_and_public
52
+ FakeWeb.register_uri :get, "http://thunk.us/pokeable",
53
+ :body => JSON.generate({ :uuid => "pokeable", :puuid => "public" })
54
+
55
+ FakeWeb.register_uri :get, "http://thunk.us/public",
56
+ :body => JSON.generate({ :uuid => "public" })
57
+
58
+ t = Thunk.for "pokeable"
59
+ assert t.pokeable?, "pokeable"
60
+ refute t.public?, "public"
61
+
62
+ t = Thunk.for "public"
63
+ refute t.pokeable?, "pokeable"
64
+ assert t.public?, "public"
65
+ end
66
+
67
+ def test_self_create
68
+ response = {
69
+ :name => "Hello",
70
+ :puuid => "public",
71
+ :state => :new,
72
+ :uuid => "uuid"
73
+ }
74
+
75
+ FakeWeb.register_uri :post, "http://thunk.us?name=Hello",
76
+ :body => JSON.generate(response)
77
+
78
+ t = Thunk.create "Hello"
79
+
80
+ response.each do |key, value|
81
+ assert_equal value, t.send(key)
82
+ end
83
+
84
+ assert_equal "http://thunk.us/uuid", t.url
85
+ end
86
+
87
+ def test_self_create_bad
88
+ FakeWeb.register_uri :post, "http://thunk.us?name=Hello",
89
+ :status => 500, :body => "[]"
90
+
91
+ t = Thunk.create "Hello"
92
+ assert_equal :unknown, t.state
93
+ end
94
+
95
+ def test_self_for
96
+ response = {
97
+ :name => "Hello",
98
+ :payload => "All is well!",
99
+ :puuid => "public",
100
+ :state => :good,
101
+ :uuid => "uuid"
102
+ }
103
+
104
+ FakeWeb.register_uri :get, "http://thunk.us/uuid",
105
+ :body => JSON.generate(response)
106
+
107
+ t = Thunk.for "uuid"
108
+
109
+ response.each do |key, value|
110
+ assert_equal value, t.send(key)
111
+ end
112
+ end
113
+
114
+ def test_self_url
115
+ assert_equal "http://thunk.us", Thunk.url
116
+
117
+ Thunk.url = "bleh"
118
+ assert_equal "bleh", Thunk.url
119
+ end
120
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thunk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Eleven Eleven
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-25 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json_pure
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: gemcutter
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.1
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: fakeweb
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.2.8
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: minitest
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.5.0
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: hoe
57
+ type: :development
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 2.5.0
64
+ version:
65
+ description: |-
66
+ An API for talking to thunk.us, a (soon to be) generally awesome
67
+ notification service. Thunk lets you share and change simple state.
68
+
69
+ We know these docs are rough, but we wanted to get a version of the
70
+ API out quickly. We'll have it all polished by 1.0, we promise. :)
71
+
72
+ Since Thunk gets used in basic infrastructure tools, it works extra
73
+ hard to never throw exceptions or cause hangs/timeouts. If it does,
74
+ please file a bug! Even a nonexistent or read-only thunk will
75
+ successfully respond to pokes, returning stub information. If you need
76
+ to make extra sure you're hooked up to a valid thunk, check out
77
+ <tt>Thunk#exists?</tt> and <tt>Thunk#pokeable?</tt>.
78
+ email:
79
+ - hello@3lev3n.com
80
+ executables:
81
+ - thunk
82
+ extensions: []
83
+
84
+ extra_rdoc_files:
85
+ - Manifest.txt
86
+ - CHANGELOG.rdoc
87
+ - README.rdoc
88
+ files:
89
+ - .autotest
90
+ - CHANGELOG.rdoc
91
+ - Manifest.txt
92
+ - README.rdoc
93
+ - Rakefile
94
+ - bin/thunk
95
+ - lib/thunk.rb
96
+ - test/test_thunk.rb
97
+ has_rdoc: true
98
+ homepage: http://github.com/eleven/thunk
99
+ licenses: []
100
+
101
+ post_install_message:
102
+ rdoc_options:
103
+ - --main
104
+ - README.rdoc
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: "0"
112
+ version:
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: "0"
118
+ version:
119
+ requirements: []
120
+
121
+ rubyforge_project: thunk
122
+ rubygems_version: 1.3.5
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: An API for talking to thunk.us, a (soon to be) generally awesome notification service
126
+ test_files:
127
+ - test/test_thunk.rb