dash-bees 0.18

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.
@@ -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