vanity-source 0.3

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/CHANGELOG ADDED
@@ -0,0 +1,16 @@
1
+ 2010-08-11 v0.3 Always be sending changes
2
+
3
+ Changed: Collector update methods accepts inc and timestamp arguments (but set
4
+ is gone).
5
+
6
+ Fixed: Rubygems source captures most recent downloads count as meta-data,
7
+ updates collector with change since last update.
8
+
9
+ Fixed: Rubygems source can handle whatever name you throw at it and properly
10
+ escapes it.
11
+
12
+ 2010-08-10 v0.2 Working Rubygems source
13
+
14
+ Fixed: Rubygems source not updating meta data like authors, project info, URL.
15
+
16
+ 2010-08-09 v0.1 First release
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org/"
2
+
3
+ group :development do
4
+ gem "rake"
5
+ gem "yard"
6
+ end
7
+
8
+ group :test do
9
+ gem "rack", "1.1.0"
10
+ gem "webmock"
11
+ end
data/MIT-LICENSE ADDED
@@ -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
+
data/README.rdoc ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require "rake/testtask"
2
+
3
+ # -- Building stuff --
4
+
5
+ spec = Gem::Specification.load(File.expand_path("vanity-source.gemspec", File.dirname(__FILE__)))
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
+ task :test
@@ -0,0 +1,167 @@
1
+ require "uri"
2
+ require "net/http"
3
+
4
+ module Vanity
5
+
6
+ # Each collector is tuned to use a given source with a given set of
7
+ # parameters. For example, a metric (collector) of Twitter followers would use
8
+ # the Twitter source and parameter telling it which Twitter account to watch.
9
+ #
10
+ #
11
+ # == Setup
12
+ #
13
+ # A collector is setup in four steps. First, the #display method returns an
14
+ # HTML form which will be displayed to the user. In fact, this method returns
15
+ # two values:
16
+ # * inputs -- HTML input controls for setting up a new controller.
17
+ # * notes -- Additional setup notes, e.g. other steps that need to be
18
+ # followed, what values to supply, etc.
19
+ #
20
+ # Wrap input controls within the label and use the source[..param..] notation
21
+ # for the name attribute. For example, for our Twitter source:
22
+ # <label>Screen name <input type="text" name="source[screen_name]" size="30"></label>
23
+ #
24
+ # When the form is filled, the #setup method is called with a context and
25
+ # values from the form fields. Since the source needs to store state
26
+ # information about the collector, it uses the context for that. The context
27
+ # is a Hash that can store basic Ruby values (nil, String, boolean, Numeric,
28
+ # Array and Hash).
29
+ #
30
+ # In particular, #setup must set two special values: name and columns. The
31
+ # first is the name for the collector, e.g. our Twitter source might produce
32
+ # the name "Twitter Mentions of @vanitydash". The second denotes all the
33
+ # columns that could be used in a metric. This value is an array, where each
34
+ # item describes a single column with a hash using the following keys:
35
+ # * id -- Column identifier (if missing, derived from column name)
36
+ # * name -- Column name (required)
37
+ #
38
+ # Shortly after #setup, the #validate method is called with the same context.
39
+ # If it raises any exception, the error is displayed to the user and the
40
+ # collector is discarded. Otherwise, #register is called with a Webhook URL.
41
+ # Some sources use that to register the Webhook with another service.
42
+ #
43
+ #
44
+ # == Updates
45
+ #
46
+ # Periodically, the source will be asked to update the collector by calling
47
+ # the #update method. This method is called with the same context and a block.
48
+ # If the source service passes data through the web hook, then #update is
49
+ # called with context, Request object and a block.
50
+ #
51
+ # The block passed to #update can be used to update the collector by yielding
52
+ # to it with a hash of named arguments. A single update may yield any number
53
+ # of times with any combination of arguments.
54
+ #
55
+ # The named arguments are:
56
+ # * inc -- Columns to increment (metric). Records a change in value which may
57
+ # be positive or negative. This is a hash where the keys are column ids (or
58
+ # indexes), the values are integers or floats.
59
+ # * timestamp -- The Time (if missing, uses the current system time).
60
+ #
61
+ # Occasionally, the #meta method is called. This method is used to display
62
+ # meta-data from the most recent state (i.e. last update). It returns an array
63
+ # of fields, each being a hash with the following values:
64
+ # * title -- Title for this field (not required)
65
+ # * text -- Text to show as value of the field
66
+ # * url -- Link (not required)
67
+ #
68
+ #
69
+ # == Teardown
70
+ #
71
+ # Eventually the collector is destroyed and the Webhook is unregistered by
72
+ # calling #unregister.
73
+ module Source
74
+
75
+ class << self
76
+ attr_accessor :logger
77
+
78
+ # Returns all available sources.
79
+ def all
80
+ @sources ||= {}
81
+ end
82
+
83
+ # Returns source by its identifier.
84
+ def find(id)
85
+ all[id]
86
+ end
87
+
88
+ # Loads all the sources from the given directory. The source identifier is
89
+ # derived from the filename (e.g. all/my_source.rb becomes "my_source"). The
90
+ # source class must map to the source identifier within the Source module,
91
+ # e.g. Vanity::Source::MySource).
92
+ def load_sources(path = File.dirname(__FILE__) + "/sources")
93
+ Dir["#{path}/*.rb"].each do |file|
94
+ id = File.basename(file, ".rb")
95
+ fail "Source #{id} already loaded" if all[id]
96
+ load file
97
+ klass = Source.const_get(id.camelize)
98
+ all[id] = klass.new
99
+ logger.info "Loaded source #{id}: #{klass}"
100
+ end
101
+ end
102
+ end
103
+
104
+ # Returns the display name for this source.
105
+ #
106
+ # Default implementation uses the class name, e.g Source::OneUp becomes "One
107
+ # Up".
108
+ def name
109
+ self.class.name.demodulize.titleize
110
+ end
111
+
112
+ # Returns additional information about this source.
113
+ #
114
+ # A good description helps the user find this source and decide whether or
115
+ # not to use it.
116
+ def description
117
+ end
118
+
119
+ # This method returns a hash with two values:
120
+ # * inputs -- HTML fragment for a setup form
121
+ # * notes -- HTML fragment for setup notes
122
+ def display
123
+ {}
124
+ end
125
+
126
+ # Called to setup a new collector with parameters from the HTML form (see
127
+ # #display). If there are any missing values, report them when #validate is
128
+ # called.
129
+ def setup(context, params)
130
+ end
131
+
132
+ # Called to validate the collector. If there are any error, raise an
133
+ # exception. A good error message will help the user understand which value
134
+ # is missing or invalid and how to correct that.
135
+ def validate(context)
136
+ end
137
+
138
+ # Called to register a Webhook (for sources that need it)
139
+ def register(context, url)
140
+ end
141
+
142
+ # Called to update the collector. This method will be called periodically
143
+ # with a context and a block. When triggered by a Webhook, it will be called
144
+ # with context, Request and a block. It can yield to the block any number of
145
+ # times with any combination of the supported named arguments for updating
146
+ # the collector.
147
+ def update(context, request, &block)
148
+ end
149
+
150
+ # Called to unregister a Webhook (for sources that don't need it).
151
+ def unregister(context, url)
152
+ end
153
+
154
+ # Returns meta-data to be displayed alongside any other data. This method
155
+ # should return an array of hashes, each with the keys title (optional),
156
+ # text and url (optional). Good meta-data provides timely and relevant
157
+ # information that is not available in the raw data.
158
+ def meta(context)
159
+ []
160
+ end
161
+
162
+ # Logger. Dump messages that can help with troubleshooting.
163
+ def logger
164
+ self.class.logger
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,62 @@
1
+
2
+ module Vanity
3
+ module Source
4
+ # Track activity (downloads and releases) of a Ruby Gem from rubygems.org.
5
+ class RubyGems
6
+ include Vanity::Source
7
+
8
+ INPUTS = <<-HTML
9
+ <label>Gem name <input type="text" name="source[gem_name]" size="30"></label>
10
+ HTML
11
+
12
+ NOTES = <<-HTML
13
+ Gem names are case sensitive
14
+ HTML
15
+
16
+ def description
17
+ "Tracks activity (downloads and releases) of a Ruby Gem from rubygems.org"
18
+ end
19
+
20
+ def display
21
+ { :inputs=>INPUTS, :notes=>NOTES }
22
+ end
23
+
24
+ def setup(context, params)
25
+ gem_name = params["gem_name"].to_s.strip
26
+ context["name"] = "RubyGems: #{gem_name}"
27
+ context["columns"] = [{ id: "downloads", label: "Downloads" }]
28
+ context["gem_name"] = gem_name
29
+ end
30
+
31
+ def validate(context)
32
+ raise "Missing gem name" if context["gem_name"].blank?
33
+ end
34
+
35
+ def update(context, webhook)
36
+ uri = URI.parse("http://rubygems.org/api/v1/gems/#{URI.escape context["gem_name"]}.json")
37
+ response = Net::HTTP.get_response(uri)
38
+ case response
39
+ when Net::HTTPOK
40
+ json = JSON.parse(response.body)
41
+ context["version"] = json["version"]
42
+ context.update json.slice(*%w{homepage_uri project_uri info authors info})
43
+ current, previous = json["downloads"], context["downloads"]
44
+ yield :inc=>{ :downloads=>(current - previous) } if previous && current > previous
45
+ context["downloads"] = current
46
+ when Net::HTTPNotFound
47
+ raise "Could not find the Gem \"#{context["gem_name"]}\""
48
+ end
49
+ end
50
+
51
+ def meta(context)
52
+ [ { title: "Project", text: context["gem_name"], url: context["homepage_uri"] || context["project_uri"] },
53
+ { text: context["info"] },
54
+ { title: "Version", text: context["version"] },
55
+ { title: "Authors", text: context["authors"] },
56
+ { title: "Downloads", text: context["downloads"] },
57
+ { title: "Source", text: "RubyGems", url: context["project_uri"] } ]
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "vanity-source"
3
+ spec.version = "0.3"
4
+ spec.author = "Assaf Arkin"
5
+ spec.email = "assaf@labnotes.org"
6
+ spec.homepage = "http://vanitydash.com"
7
+ spec.summary = "Data sources for metrics and events"
8
+
9
+ spec.files = Dir["{bin,lib,test}/**/*", "CHANGELOG", "MIT-LICENSE", "README.rdoc", "Rakefile", "Gemfile", "vanity-source.gemspec"]
10
+
11
+ spec.has_rdoc = true
12
+ spec.extra_rdoc_files = "README.rdoc", "CHANGELOG"
13
+ spec.rdoc_options = "--title", "Vanity::Source #{spec.version}", "--main", "README.rdoc",
14
+ "--webcvs", "http://github.com/assaf/#{spec.name}"
15
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vanity-source
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ version: "0.3"
10
+ platform: ruby
11
+ authors:
12
+ - Assaf Arkin
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-11 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: assaf@labnotes.org
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ - CHANGELOG
30
+ files:
31
+ - lib/vanity/source.rb
32
+ - lib/vanity/sources/ruby_gems.rb
33
+ - CHANGELOG
34
+ - MIT-LICENSE
35
+ - README.rdoc
36
+ - Rakefile
37
+ - Gemfile
38
+ - vanity-source.gemspec
39
+ has_rdoc: true
40
+ homepage: http://vanitydash.com
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --title
46
+ - Vanity::Source 0.3
47
+ - --main
48
+ - README.rdoc
49
+ - --webcvs
50
+ - http://github.com/assaf/vanity-source
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ hash: 3
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.3.7
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Data sources for metrics and events
78
+ test_files: []
79
+