dash-bees 0.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,116 @@
1
+ 2010-09-16 v0.18 Now bees, have separate update and webhook methods
2
+
3
+ Update method separated from webhook method.
4
+
5
+ Bees for being busy workers.
6
+
7
+ 2010-09-12 v0.17 API change to callback and HTTP access
8
+
9
+ Changed API to use callback object instead of block with too many argument
10
+ combinations.
11
+
12
+ Clarified and validating that source state can only use alphanumeric/underscore
13
+ in field names.
14
+
15
+ Added wrapper around HTTP API to make it easier to switch to asynchronous
16
+ processing later on.
17
+
18
+ 2010-09-08 v0.16 To name a source, set source.name
19
+
20
+ During setup, name the source by setting source.name, not metric.name.
21
+
22
+ Revised and improved test suite.
23
+
24
+ 2010-09-07 v0.15 Renamed to DashFu::Mario
25
+
26
+ 2010-09-07 v0.14 RubyGems and Github identities
27
+
28
+ RubyGems releases now identify RubyGems as the source of the activity.
29
+
30
+ Github source includes github login name as identity.
31
+
32
+ 2010-08-31 v0.13 Github data source merges related commits
33
+
34
+ Now it does.
35
+
36
+ 2010-08-31 v0.12 Github data source merges related commits
37
+
38
+ Github data source merges related commits (same committer/time) into single
39
+ activity and shows first line of all commit messages.
40
+
41
+ Changed to coding/testing with Ruby 1.9.2, since this is the target
42
+ environment.
43
+
44
+ 2010-08-30 v0.11 Changes to activity person, AS 3.0 support
45
+
46
+ Activity's person name is now fullname, photo is photo_url and url is no
47
+ longer, but we do accept multiple identities of the form domain:id, e.g
48
+ twitter.com:assaf, linkedin.com:assafarkin.
49
+
50
+ Test suite now allows validating metric, activity and person. Call the validate
51
+ method to run validation and raise exception on error (with a helpful error
52
+ message). Call valid? if you're only interested in true/false.
53
+
54
+ Gem dependencies removed to allow running with ActiveSupport 3.0.
55
+
56
+ 2010-08-26 v0.10 Tags and better messages
57
+
58
+ Activities can now include tags.
59
+
60
+ Improvement all around to all the HTML messages.
61
+
62
+ 2010-08-25 v0.9 Better Github commit message
63
+
64
+ 2010-08-24 v0.8 Minor API change
65
+
66
+ Attribute :id is now :uid.
67
+
68
+ Activity content can be set using either :html or :text attribute.
69
+ 2010-08-24 v0.7 Added test suite and resources
70
+
71
+ Each source can have an associated resources file (YAML), e.g. ruby_gems.rb
72
+ would have ruby_gems.yml. The default implementation of methods like name,
73
+ description and display pull content from the resource. Resources can support
74
+ multiple languages, but we're starting with just EN.
75
+
76
+ Some sources also need API keys. These are accessed using the method api_key
77
+ that returns the API key value for the current source (often a string, but can
78
+ also be hash or array). There's an api_keys.yml file which contains fake API
79
+ keys. You can put a real key in there while generating a cassette for your test
80
+ case.
81
+
82
+ Test suite is here, using WebMock and VCR to rest API calls. Cassettes go in
83
+ test/cassettes, fixtures in test/fixtures.
84
+
85
+ 2010-08-19 v0.6 Added Backtweet
86
+
87
+ 2010-08-19 v0.5 Added Github and Github issues
88
+
89
+ 2010-08-18 v0.4 Metrics that set and increment
90
+
91
+ Revised setup method to change special values metric.name and metric.columns
92
+ (was name and column).
93
+
94
+ There are two types of metrics, those that collect totals and those that
95
+ collect daily/hourly values. The setup method indicates that by setting the
96
+ value metric.total (defaults to false).
97
+
98
+ Revised update method to allow either setting current value (:set) or
99
+ incrementing current value (:inc).
100
+
101
+ 2010-08-11 v0.3 Always be sending changes
102
+
103
+ Changed: Collector update methods accepts inc and timestamp arguments (but set
104
+ is gone).
105
+
106
+ Fixed: Rubygems source captures most recent downloads count as meta-data,
107
+ updates collector with change since last update.
108
+
109
+ Fixed: Rubygems source can handle whatever name you throw at it and properly
110
+ escapes it.
111
+
112
+ 2010-08-10 v0.2 Working Rubygems source
113
+
114
+ Fixed: Rubygems source not updating meta data like authors, project info, URL.
115
+
116
+ 2010-08-09 v0.1 First release
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org/"
2
+ gemspec
3
+
4
+ group :development do
5
+ gem "rake"
6
+ gem "yard"
7
+ end
8
+
9
+ group :test do
10
+ gem "awesome_print"
11
+ gem "nokogiri"
12
+ gem "shoulda"
13
+ gem "timecop"
14
+ gem "vcr"
15
+ gem "webmock"
16
+ end
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 Assaf Arkin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,113 @@
1
+ {Dash-fu}[http://dash-fu.com] sources need to get their data somehow: enter
2
+ Bees. Bees are the brave workers that go out and collect data from various
3
+ data sources and report them back in the form of activity stream, metrics and
4
+ status updates.
5
+
6
+
7
+ == Setup
8
+
9
+ Dash-fu creates data sources using a four-step process. In the first step, it
10
+ calls the display method to get HTML fragments and display these to the user.
11
+
12
+ Currently it uses two fragments:
13
+
14
+ * inputs -- HTML input controls for setting up a new controller.
15
+ * notes -- Additional setup notes: other steps that need to be
16
+ followed, what values to supply, etc.
17
+
18
+ Input controls use names of the form source[..param..], and wrapped inside label
19
+ elements. For example:
20
+
21
+ <label>Screen name <input type="text" name="source[screen_name]" size="30"></label>
22
+
23
+ Once the user fills and submits the form, Dash-fu creates a new empty state
24
+ object and calls the setup method with that object and the input fields.
25
+
26
+ The state object is a Hash than can store basic values: nil, string, boolean,
27
+ numerics, arrays and other hashes. Key names must be alphanumeric/underscore.
28
+ Bees use it to store state in between method calls.
29
+
30
+ The setup method must set one value, "source.name", to suggest a name for this
31
+ source, e.g. "Twitter mentions for @dash_fu".
32
+
33
+ In the third state, Dash-fu calls the validate method. If this method raises an
34
+ exceptions, the error message is shown to the user and the source discarded. The
35
+ fourth step involves registering callbacks (not currently supported).
36
+
37
+
38
+ == Metrics
39
+
40
+ For sources that include metrics, the setup method specifies the follow values
41
+ during the call to setup:
42
+
43
+ * metric.columns -- Columns (at least one) as an array of hashes, see
44
+ attributes names below.
45
+ * metric.totals -- True if the metric collects life-time totals (e.g.
46
+ downloads, uptime), false if it only collects recent values (e.g. average
47
+ response time).
48
+
49
+ Columns are specified using the following attributes:
50
+
51
+ * id -- Column identifier (if missing, derived from column name)
52
+ * name -- Column name (required)
53
+
54
+
55
+ == Updates
56
+
57
+ Periodically, all sources are updated by calling their update method. This
58
+ method taks two arguments, the source state object and an object wrapping
59
+ several callback methods.
60
+
61
+ A single update can call any combination of callback methods, for example, it
62
+ can set values in a metric and record an activity.
63
+
64
+ The callback methods are:
65
+
66
+ * set! -- Sets the most recent value for this metric. The single argument is a
67
+ hash, where key names are either column ids or indices, and values are
68
+ integers or floats.
69
+ * inc! -- Changes the most recent value for this metric. The single argument is
70
+ a hash, where key names are either column ids or indices, and values are
71
+ integers or floats.
72
+ * activity! -- Adds an activity to the stream, see below for details about the
73
+ argument.
74
+ * error! -- Records a processing error. The single argument is an error
75
+ message. Since updates are processed asynchronously, use this method to
76
+ indicate any processing error, don't raise an exception.
77
+
78
+ Activities have the following attributes:
79
+
80
+ * uid -- Unique identifier (within the scope of this source). Optional.
81
+ * html -- HTML contents of the activity.
82
+ * text -- Text contents of the activity (alternative to html attribute).
83
+ * url -- Points back to the original resource. Optional.
84
+ * tags -- Any number of tags for identifying related activities. Tags are
85
+ lower-cased and can contain alphanumeric and dashes. Optional.
86
+ * person -- Person who performed this activity (see below).
87
+ * timestamp -- Timestamp activity occurred. Optional.
88
+
89
+ HTML can use links (HTTP/S and email), images, bold, italics, paragraphs, block
90
+ quotes, lists, tables, pre-formatted text and even video. Scripts, objects and
91
+ frames are stripped out, as are any script and style attributes.
92
+
93
+ People are identified by the following attributes:
94
+
95
+ * fullname -- What it says on the label.
96
+ * email -- Email address if known.
97
+ * identities -- Any number of identities, in the form domain:id, e.g.
98
+ twitter.com:assaf or linkedin.com:assafarkin.
99
+ * photo_url -- Preferrably 48x48.
100
+
101
+ In addition, from time to time, Dash-fu would call the meta method and display
102
+ the returned fields. This method returns an array of hashes, each describing a
103
+ single display value using the following attributes.
104
+
105
+ * title -- Title to show next to the value (optional).
106
+ * text -- Text to show as value of this field.
107
+ * url -- Link (optional).
108
+
109
+
110
+ == Webhooks
111
+
112
+ TBD
113
+
@@ -0,0 +1,39 @@
1
+ require "rake/testtask"
2
+
3
+ # -- Building stuff --
4
+
5
+ spec = Gem::Specification.load(Dir["*.gemspec"].first)
6
+
7
+ desc "Build the Gem"
8
+ task :build do
9
+ sh "gem build #{spec.name}.gemspec"
10
+ end
11
+
12
+ desc "Install #{spec.name} locally"
13
+ task :install=>:build do
14
+ sudo = "sudo" unless File.writable?( Gem::ConfigMap[:bindir])
15
+ sh "#{sudo} gem install #{spec.name}-#{spec.version}.gem"
16
+ end
17
+
18
+ desc "Push new release to gemcutter and git tag"
19
+ task :push=>["test", "build"] do
20
+ sh "git push"
21
+ puts "Tagging version #{spec.version} .."
22
+ sh "git tag v#{spec.version}"
23
+ sh "git push --tag"
24
+ puts "Building and pushing gem .."
25
+ sh "gem push #{spec.name}-#{spec.version}.gem"
26
+ end
27
+
28
+
29
+ task :default=>:test
30
+ Rake::TestTask.new do |task|
31
+ task.test_files = FileList['test/**/*_test.rb']
32
+ #task.warning = true
33
+ task.verbose = true
34
+ end
35
+
36
+ task :clobber do
37
+ rm_rf "doc"
38
+ rm "test/test.log"
39
+ end
@@ -0,0 +1,25 @@
1
+ $: << File.dirname(__FILE__) + "/lib"
2
+ require "dash-fu/bee"
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "dash-bees"
6
+ spec.version = DashFu::Bee::VERSION
7
+ spec.author = "Assaf Arkin"
8
+ spec.email = "assaf@labnotes.org"
9
+ spec.homepage = "http://dash-fu.com"
10
+ spec.summary = "Hopping around collecting data, keeping your dashes dashing"
11
+
12
+ spec.files = Dir["{bin,lib,test}/**/*", "CHANGELOG", "MIT-LICENSE", "README.rdoc", "Rakefile", "Gemfile", "*.gemspec"]
13
+
14
+ spec.has_rdoc = true
15
+ spec.extra_rdoc_files = "README.rdoc", "CHANGELOG"
16
+ spec.rdoc_options = "--title", "DashFu::Bee #{spec.version}", "--main", "README.rdoc",
17
+ "--webcvs", "http://github.com/assaf/#{spec.name}"
18
+
19
+ spec.required_ruby_version = '>= 1.8.7'
20
+ spec.add_dependency "activesupport"
21
+ spec.add_dependency "json"
22
+ spec.add_dependency "nokogiri"
23
+ spec.add_dependency "rack"
24
+ spec.add_dependency "i18n"
25
+ end
@@ -0,0 +1,212 @@
1
+ require "active_support"
2
+ require "json"
3
+ require "net/http"
4
+ require "rack"
5
+ require "uri"
6
+
7
+ # See http://dash-fu.com
8
+ module DashFu
9
+
10
+ # The README covers it all.
11
+ module Bee
12
+
13
+ VERSION = "0.18"
14
+
15
+ class << self
16
+ attr_accessor :logger
17
+
18
+ # Returns all available bees.
19
+ def all
20
+ @bees ||= {}
21
+ end
22
+
23
+ # Returns bee by its identifier.
24
+ def find(id)
25
+ all[id]
26
+ end
27
+
28
+ # Loads all the bees from the given directory. The bee identifier is
29
+ # derived from the filename (e.g. all/get_this.rb becomes "get_this"). The
30
+ # bee class must map to the source identifier within the Bee module, e.g.
31
+ # DashFu::Bee::GetThis).
32
+ def load_bees(path = File.dirname(__FILE__) + "/bees")
33
+ Dir["#{path}/*.rb"].each do |file|
34
+ id = File.basename(file, ".rb")
35
+ fail "Bee #{id} already loaded" if all[id]
36
+ load file
37
+ klass = Bee.const_get(id.camelize)
38
+ all[id] = klass.new
39
+ logger.info "Loaded Bee #{id}: #{klass}"
40
+ end
41
+ end
42
+
43
+ # API keys (see instance method api_key).
44
+ attr_accessor :api_keys
45
+
46
+ def included(klass)
47
+ klass.extend ClassMethods
48
+ end
49
+ end
50
+
51
+
52
+ module ClassMethods
53
+ # Bee identifier.
54
+ def bee_id
55
+ @bee_id ||= name.demodulize.underscore
56
+ end
57
+ end
58
+
59
+
60
+ # Returns the display name for this Bee.
61
+ #
62
+ # Uses the resource 'name', and fallbacks on the class name (e.g
63
+ # Bee::OneUp becomes "One Up").
64
+ def name
65
+ resources["name"] || bee_id.titleize
66
+ end
67
+
68
+ # Returns additional information about this source.
69
+ #
70
+ # A good description helps the user identity source and decide whether or
71
+ # not to use it.
72
+ #
73
+ # Uses the resource 'description'.
74
+ def description
75
+ resources["description"]
76
+ end
77
+
78
+ # This method returns a hash with two values:
79
+ # * inputs -- HTML fragment for a setup form
80
+ # * notes -- HTML fragment for setup notes
81
+ #
82
+ # Uses the resources 'inputs' and 'notes'.
83
+ def display
84
+ { :inputs=>resources["inputs"], :notes=>resources["notes"] }
85
+ end
86
+
87
+ # Called to setup a new source with parameters from the HTML form (see
88
+ # #display). If there are any missing values, report them when #validate is
89
+ # called.
90
+ def setup(source, params)
91
+ end
92
+
93
+ # Called to validate the source. If there are any error, raise an
94
+ # exception. A good error message will help the user understand which value
95
+ # is missing or invalid and how to correct that.
96
+ def validate(source)
97
+ end
98
+
99
+ # Called to register a Webhook (for sources that need it)
100
+ def register(source, url)
101
+ end
102
+
103
+ # Called to update the source. It can invoke callback methods any number of
104
+ # times with any combination of the supported named arguments for updating
105
+ # the source.
106
+ def update(source, callbacks)
107
+ end
108
+
109
+ # Called in response to (previously registered) Webhook request. Second
110
+ # argument is a Rack::Request. It can invoke callback methods any number of
111
+ # times with any combination of the supported named arguments for updating
112
+ # the source.
113
+ def webhook(source, request, callback)
114
+ end
115
+
116
+ # Called to unregister a Webhook (for sources that don't need it).
117
+ def unregister(source, url)
118
+ end
119
+
120
+ # Returns meta-data to be displayed alongside any other data. This method
121
+ # should return an array of hashes, each with the keys title (optional),
122
+ # text and url (optional). Good meta-data provides timely and relevant
123
+ # information that is not available in the raw data.
124
+ def meta(source)
125
+ []
126
+ end
127
+
128
+ # Use this to make HTTP request to external services. This method opens a
129
+ # new session and yields to the block. The block can them make HTTP requests
130
+ # on the remote host. The block may be called asynchronously.
131
+ def session(host, port = 80)
132
+ http = Net::HTTP.new(host, port)
133
+ http.use_ssl = true if port == 443
134
+ http.start do |http|
135
+ yield Session.new(http)
136
+ end
137
+ end
138
+
139
+ # HTTP Session.
140
+ class Session
141
+ def initialize(http) #:nodoc:
142
+ @http = http
143
+ end
144
+
145
+ # Make a GET request and yield response to the block. Response consists of
146
+ # three arguments: status code, response body and response headers. The
147
+ # block may be called asynchronoulsy.
148
+ def get(path, headers = {}, &block)
149
+ response = @http.request(get_request(path, headers))
150
+ yield response.code, response.body, {}
151
+ end
152
+
153
+ # Make a GET request and yield response to the block. If the response
154
+ # status is 200 the second argument is the response JSON object. The block
155
+ # may be called asynchronously.
156
+ def get_json(path, headers = {}, &block)
157
+ response = @http.request(get_request(path, headers))
158
+ if Net::HTTPOK === response
159
+ json = JSON.parse(response.body) rescue nil
160
+ if json
161
+ yield response.code.to_i, json, {}
162
+ else
163
+ yield 500, "Not a JSON document", {}
164
+ end
165
+ else
166
+ yield response.code.to_i, response.message, {}
167
+ end
168
+ end
169
+
170
+ protected
171
+
172
+ def get_request(path, headers)
173
+ request = Net::HTTP::Get.new(path)
174
+ request.basic_auth headers[:username], headers[:password] if headers[:username] && headers[:password]
175
+ request
176
+ end
177
+ end
178
+
179
+ protected
180
+
181
+ # Source identifier.
182
+ def bee_id
183
+ self.class.bee_id
184
+ end
185
+
186
+ # Logger. Dump messages that can help with troubleshooting.
187
+ def logger
188
+ DashFu::Bee.logger
189
+ end
190
+
191
+ # Returns I18n resources for this gem.
192
+ def resources
193
+ unless @resources
194
+ file_name = File.dirname(__FILE__) + "/bees/#{bee_id}.yml"
195
+ @resources = File.exist?(file_name) ? YAML.load_file(file_name)["en"] : {}
196
+ end
197
+ @resources
198
+ end
199
+
200
+ # Shortcut for Rack::Utils.escape_html
201
+ def escape_html(text)
202
+ Rack::Utils.escape_html(text.to_s)
203
+ end
204
+ alias :h :escape_html
205
+
206
+ # Returns API key for this source. May return string or hash, depending on
207
+ # the API.
208
+ def api_key
209
+ @api_key ||= DashFu::Bee.api_keys[bee_id] or raise "No API key for #{bee_id}"
210
+ end
211
+ end
212
+ end