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 +16 -0
- data/Gemfile +11 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +0 -0
- data/Rakefile +28 -0
- data/lib/vanity/source.rb +167 -0
- data/lib/vanity/sources/ruby_gems.rb +62 -0
- data/vanity-source.gemspec +15 -0
- metadata +79 -0
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
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
|
+
|