projector 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -3
- data/Gemfile +15 -1
- data/LICENSE +13 -0
- data/README.md +13 -36
- data/Rakefile +28 -0
- data/bin/console +6 -0
- data/bin/setup +7 -0
- data/circle.yml +3 -0
- data/lib/projector/api_methods.rb +30 -0
- data/lib/projector/client.rb +46 -0
- data/lib/projector/end_user.rb +27 -0
- data/lib/projector/envelope.rb +22 -0
- data/lib/projector/error.rb +61 -0
- data/lib/projector/event.rb +19 -0
- data/lib/projector/transport/http.rb +203 -0
- data/lib/projector/version.rb +1 -1
- data/lib/projector.rb +54 -4
- data/projector.gemspec +18 -19
- data/sample/Gemfile +6 -0
- data/sample/Gemfile.lock +37 -0
- data/sample/README.md +30 -0
- data/sample/config.ru +3 -0
- data/sample/config.yml +2 -0
- data/sample/index.rb +101 -0
- metadata +62 -79
- data/bin/projector +0 -17
- data/lib/projector/cli.rb +0 -86
- data/lib/projector/helpers.rb +0 -76
- data/lib/projector/repo.rb +0 -34
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c2b3dbd1610c6ce053ccd4e5a0566b6f77475ff5
|
4
|
+
data.tar.gz: d39d7bcb4f57f8e9970de3b461db9b71f4ae4a75
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a716767bae171e383ccc6ce0081bd7dcc520efabf3a6134e5556d1e08e87ba7dc14131b1347f71609b229ac115bdc170c83d126fa31d2b1f77a8d420144c42c5
|
7
|
+
data.tar.gz: ab4278cfe220f32606c78a457414a57af849e5bf6dd4f10512e48668b194a28cfdf0ee5aea05cb76aa2d738490233c0c7464c4ff7cfd96ce57394d4bf1f31a93
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,4 +1,18 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in projector.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
gem 'rake', :group => [:development, :test]
|
7
|
+
|
8
|
+
# Testing dependencies
|
9
|
+
group :test do
|
10
|
+
gem 'minitest'
|
11
|
+
gem 'webmock', :require => 'webmock/minitest'
|
12
|
+
gem 'mocha', :require => 'mocha/setup'
|
13
|
+
gem 'simplecov', :require => false
|
14
|
+
gem 'm'
|
15
|
+
gem 'addressable'
|
16
|
+
gem 'factory_girl'
|
17
|
+
gem 'timecop'
|
18
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2016, Orphid, Inc.
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
8
|
+
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
10
|
+
|
11
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
CHANGED
@@ -1,44 +1,21 @@
|
|
1
|
-
# Projector
|
1
|
+
# Projector Ruby SDK
|
2
2
|
|
3
|
-
|
3
|
+
[SDK Guide](https://goprojector.readme.io/docs/ruby-1)
|
4
4
|
|
5
|
-
##
|
5
|
+
## Installation
|
6
6
|
|
7
|
-
|
7
|
+
The `projector` gem is hosted on Gemfury so you will need to add the Gemfury repository to your gemfile:
|
8
8
|
|
9
|
-
|
9
|
+
```
|
10
|
+
gem 'projector', :source => 'https://gem.fury.io/projector/'
|
11
|
+
```
|
10
12
|
|
11
|
-
Install the gem:
|
12
13
|
|
13
|
-
|
14
|
+
## Building / Testing
|
14
15
|
|
15
|
-
|
16
|
+
### Testing
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
git config --global projector.workingdir ~/Projects
|
23
|
-
|
24
|
-
Run `projector checkout`. Projector will find all of the repos you have access to and prompt you to clone them under your working directory if they're not already cloned. By default, it will create a nested directory structure based on the repository owner, something like this:
|
25
|
-
|
26
|
-
jayzes/
|
27
|
-
jayzes/projector
|
28
|
-
jayzes/cucumber-api-steps
|
29
|
-
gvarela/
|
30
|
-
gvarela/food_court_recipes
|
31
|
-
|
32
|
-
If you want it to forge ahead and clone everything, there's a `-a` option that assumes yes to every clone confirmation and doesn't bother prompting.
|
33
|
-
|
34
|
-
## Future Ideas
|
35
|
-
* Skiplist/repo ignore regexes
|
36
|
-
* Checking for repos that aren't under version control and prompting to create or prune them
|
37
|
-
* An easier way to run this under cron
|
38
|
-
|
39
|
-
# Author
|
40
|
-
Jay Zeschin
|
41
|
-
|
42
|
-
## Copyright
|
43
|
-
|
44
|
-
Copyright (c) 2011 Jay Zeschin. Distributed under the MIT License.
|
18
|
+
```
|
19
|
+
bin/setup
|
20
|
+
bundle exec run test
|
21
|
+
```
|
data/Rakefile
CHANGED
@@ -1,2 +1,30 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.pattern = 'test/**/*_test.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :console do
|
11
|
+
require 'irb'
|
12
|
+
require 'irb/completion'
|
13
|
+
require 'projector'
|
14
|
+
ARGV.clear
|
15
|
+
IRB.start
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => :test
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'yard'
|
22
|
+
YARD::Rake::YardocTask.new(:doc) do |task|
|
23
|
+
task.files = ['Readme.markdown', 'LICENSE', 'lib/**/*.rb']
|
24
|
+
task.options = [
|
25
|
+
'--output-dir', 'doc',
|
26
|
+
'--markup', 'markdown',
|
27
|
+
]
|
28
|
+
end
|
29
|
+
rescue LoadError
|
30
|
+
end
|
data/bin/console
ADDED
data/bin/setup
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Projector
|
2
|
+
module ApiMethods
|
3
|
+
def register_end_user(end_user)
|
4
|
+
# If a hash is passed, use it to populate a user object
|
5
|
+
if end_user.instance_of?(Hash)
|
6
|
+
user = Projector::EndUser.new(end_user)
|
7
|
+
# If a User instance is passed, use it
|
8
|
+
elsif end_user.instance_of?(Projector::EndUser)
|
9
|
+
user = end_user
|
10
|
+
end
|
11
|
+
# return the data element of the resulting envelope
|
12
|
+
post(host, '/end_users', Projector::Envelope.new(Projector::EndUser::SCHEMA, user).to_hash).body
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_end_user_tags(id, tags)
|
16
|
+
user = Projector::EndUser.new(id: id, tags: tags)
|
17
|
+
register_end_user(user)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deliver_event(event)
|
21
|
+
# Create an Event instance if the parameter is a Hash.
|
22
|
+
if event.instance_of?(Hash)
|
23
|
+
event = Projector::Event.new(event)
|
24
|
+
end
|
25
|
+
# Verify that we've got an Event object.
|
26
|
+
raise Projector::InvalidEvent.new("Invalid event data") unless event.instance_of?(Projector::Event)
|
27
|
+
post(host, '/events', Projector::Envelope.new(Projector::Event::SCHEMA, event).to_hash).body
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Projector
|
2
|
+
require 'projector/transport/http'
|
3
|
+
require 'projector/error'
|
4
|
+
require 'projector/envelope'
|
5
|
+
require 'projector/end_user'
|
6
|
+
require 'projector/event'
|
7
|
+
require 'projector/api_methods'
|
8
|
+
|
9
|
+
class Client
|
10
|
+
include Projector::ApiMethods
|
11
|
+
include Projector::Transport::HTTP
|
12
|
+
|
13
|
+
attr_accessor :host, :token, :result_format
|
14
|
+
|
15
|
+
# The current configuration parameters for all clients
|
16
|
+
def self.options
|
17
|
+
@options ||= {
|
18
|
+
host: ENV['PROJECTOR_API_HOST'] || 'https://api.projector.com'
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the current configuration parameters for all clients
|
23
|
+
def self.options=(val)
|
24
|
+
@options = val
|
25
|
+
end
|
26
|
+
|
27
|
+
# Allows the setting of configuration parameters in a configure block.
|
28
|
+
def self.configure
|
29
|
+
yield options
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initialize a new client.
|
33
|
+
#
|
34
|
+
# @param options [Hash] optionally specify `:access_token`, `:host`, `:api_version`
|
35
|
+
def initialize(options = {})
|
36
|
+
options = self.class.options.merge(options)
|
37
|
+
@host = options[:host] || ENV['PROJECTOR_API_HOST']
|
38
|
+
@token = options[:token] || ENV['PROJECTOR_API_TOKEN']
|
39
|
+
@options = options
|
40
|
+
end
|
41
|
+
|
42
|
+
def host
|
43
|
+
@options[:host]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Projector
|
2
|
+
class EndUser
|
3
|
+
|
4
|
+
SCHEMA = 'http://schemas.projector.com/schemas/com.projector/server_api/jsonschema/0-9-1#/definitions/end_user_post'
|
5
|
+
|
6
|
+
attr_accessor :id, :tags
|
7
|
+
|
8
|
+
def initialize(params = {})
|
9
|
+
raise Projector::InvalidEndUser.new("Invalid end user, id missing") if params[:id].nil?
|
10
|
+
if params.instance_of?(Hash)
|
11
|
+
params.each do |k, v|
|
12
|
+
self.send("#{k}=", v) if self.respond_to?("#{k}=")
|
13
|
+
end
|
14
|
+
else
|
15
|
+
self.id = params.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{
|
21
|
+
customer_end_user_id: id.to_s,
|
22
|
+
tags: (tags || []),
|
23
|
+
profile: {}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Projector
|
2
|
+
class Envelope
|
3
|
+
attr_accessor :schema, :data
|
4
|
+
|
5
|
+
def initialize(schema, data)
|
6
|
+
@schema = schema
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def payload
|
11
|
+
data.respond_to?(:to_hash) ? data.to_hash : data
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash(params = {})
|
15
|
+
raise Projector::InvalidEnvelope unless @data && @schema
|
16
|
+
{
|
17
|
+
schema: schema,
|
18
|
+
data: payload
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Projector
|
2
|
+
# Standard Projector error
|
3
|
+
class Error < StandardError
|
4
|
+
attr_accessor :request, :response, :response_body, :request_id
|
5
|
+
end
|
6
|
+
|
7
|
+
# Raised when Projector returns a 400 HTTP status code
|
8
|
+
class BadRequest < Error; end
|
9
|
+
|
10
|
+
# Raised when Projector returns a 401 HTTP status code
|
11
|
+
class Unauthorized < Error; end
|
12
|
+
|
13
|
+
# Raised when Projector returns a 403 HTTP status code
|
14
|
+
class Forbidden < Error; end
|
15
|
+
|
16
|
+
# Raised when Projector returns a 404 HTTP status code
|
17
|
+
class NotFound < Error; end
|
18
|
+
|
19
|
+
# Raised when Projector returns a 406 HTTP status code
|
20
|
+
class NotAcceptable < Error; end
|
21
|
+
|
22
|
+
# Raised when Projector returns a 422 HTTP status code
|
23
|
+
class UnprocessableEntity < Error; end
|
24
|
+
|
25
|
+
# Raised when Projector returns a 500 HTTP status code
|
26
|
+
class InternalServerError < Error;
|
27
|
+
attr_accessor :retry_after
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raised when Projector returns a 501 HTTP status code
|
31
|
+
class NotImplemented < Error; end
|
32
|
+
|
33
|
+
# Raised when Projector returns a 502 HTTP status code
|
34
|
+
class BadGateway < Error; end
|
35
|
+
|
36
|
+
# Raised when Projector returns a 503 HTTP status code
|
37
|
+
class ServiceUnavailable < Error; end
|
38
|
+
|
39
|
+
# Raised when a unique ID is required but not provided
|
40
|
+
class UniqueIDRequired < Error; end
|
41
|
+
|
42
|
+
class InvalidEndUser < StandardError; end
|
43
|
+
class InvalidEvent < StandardError; end
|
44
|
+
class InvalidDevice < StandardError; end
|
45
|
+
class InvalidRegistration < StandardError; end
|
46
|
+
class InvalidEnvelope < StandardError; end
|
47
|
+
|
48
|
+
# Status code to exception map
|
49
|
+
ERROR_MAP = {
|
50
|
+
400 => Projector::BadRequest,
|
51
|
+
401 => Projector::Unauthorized,
|
52
|
+
403 => Projector::Forbidden,
|
53
|
+
404 => Projector::NotFound,
|
54
|
+
406 => Projector::NotAcceptable,
|
55
|
+
422 => Projector::UnprocessableEntity,
|
56
|
+
500 => Projector::InternalServerError,
|
57
|
+
501 => Projector::InternalServerError,
|
58
|
+
502 => Projector::InternalServerError,
|
59
|
+
503 => Projector::InternalServerError
|
60
|
+
}
|
61
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Projector
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
class Event
|
5
|
+
SCHEMA = 'http://schemas.projector.com/schemas/com.projector/events_api/jsonschema/0-9-3#/links/0/schema'
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
@params = params.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
overrides = {
|
13
|
+
id: @params[:id] ? @params[:id].to_s : SecureRandom.uuid,
|
14
|
+
event_time: ((@params[:event_time].instance_of?(Time) ? @params[:event_time] : Time.now).to_f * 1000).to_i
|
15
|
+
}
|
16
|
+
@params.merge overrides
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module Projector
|
2
|
+
module Transport
|
3
|
+
# Persistent HTTP API transport adapter
|
4
|
+
module HTTP
|
5
|
+
require 'net/http/persistent'
|
6
|
+
require 'uri'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
MAX_REDIRECTS = 2
|
10
|
+
|
11
|
+
[:get, :post, :put, :delete, :patch].each do |method|
|
12
|
+
define_method method do |*args|
|
13
|
+
json_request(method, *args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def request(method, host, path, params = nil, redirect_count = 0)
|
20
|
+
uri = URI.parse("#{host}#{path}")
|
21
|
+
|
22
|
+
# if the request requires parameters in the query string, merge them in
|
23
|
+
if params && !can_post_data?(method)
|
24
|
+
query_values = uri.query ? URI.decode_www_form(uri.query).inject({}) {|h,(k,v)| h[k]=v; h} : {}
|
25
|
+
uri.query = to_url_params((query_values || {}).merge(params))
|
26
|
+
end
|
27
|
+
|
28
|
+
# Build request
|
29
|
+
request = build_request(method, uri)
|
30
|
+
|
31
|
+
# Add headers
|
32
|
+
request['Authorization'] = "Token #{self.token}"
|
33
|
+
request['Content-Type'] = 'application/json'
|
34
|
+
request['Accept'] = "application/vnd.projector+json;"
|
35
|
+
request['Request-Id'] = SecureRandom.uuid
|
36
|
+
|
37
|
+
# Add params as JSON if they exist
|
38
|
+
request.body = JSON.generate(params) if can_post_data?(method) && params
|
39
|
+
|
40
|
+
log_debug_data(uri, request.method, request.body)
|
41
|
+
|
42
|
+
# Request
|
43
|
+
response = http.request(uri, request)
|
44
|
+
|
45
|
+
# Log debug data if needed
|
46
|
+
log_debug_data(uri, response.code, response.body)
|
47
|
+
|
48
|
+
# If we've been given a redirect
|
49
|
+
if [301, 302].include?(response.code.to_i)
|
50
|
+
|
51
|
+
# Make sure we aren't in a giant redirect loop
|
52
|
+
if redirect_count >= MAX_REDIRECTS
|
53
|
+
err = TooManyRedirectsError.new
|
54
|
+
err.request = request
|
55
|
+
err.response = response
|
56
|
+
err.redirect_count = redirect_count
|
57
|
+
raise err
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parse the new location
|
61
|
+
uri = URI.parse(response['Location'])
|
62
|
+
# Make the call to the redirected location, incrementing the redirect count by 1
|
63
|
+
return request(method, uri.select(:scheme, :host).join("://"), uri.path, params, redirect_count + 1)
|
64
|
+
# Return the result of the redirected request
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check for errors
|
68
|
+
handle_error(request, response)
|
69
|
+
|
70
|
+
|
71
|
+
# Return the raw response object
|
72
|
+
response
|
73
|
+
end
|
74
|
+
|
75
|
+
def http
|
76
|
+
@http ||= begin
|
77
|
+
http = Net::HTTP::Persistent.new('projector')
|
78
|
+
http.open_timeout = 5
|
79
|
+
http.read_timeout = 2
|
80
|
+
http
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_request(method, uri)
|
85
|
+
case method
|
86
|
+
when :get
|
87
|
+
Net::HTTP::Get.new(uri.request_uri)
|
88
|
+
when :post
|
89
|
+
Net::HTTP::Post.new(uri.request_uri)
|
90
|
+
when :put
|
91
|
+
Net::HTTP::Put.new(uri.request_uri)
|
92
|
+
when :delete
|
93
|
+
Net::HTTP::Delete.new(uri.request_uri)
|
94
|
+
when :patch
|
95
|
+
Net::HTTP::Patch.new(uri.request_uri)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_url_params(hash)
|
100
|
+
params = []
|
101
|
+
hash.each_pair do |key, value|
|
102
|
+
params << param_for(key, value).flatten
|
103
|
+
end
|
104
|
+
params.sort.join('&')
|
105
|
+
end
|
106
|
+
|
107
|
+
def param_for(key, value, parent = nil)
|
108
|
+
if value.is_a?(Hash)
|
109
|
+
params = []
|
110
|
+
value.each_pair do |value_key, value_value|
|
111
|
+
value_parent = parent ? parent + "[#{key}]" : key.to_s
|
112
|
+
params << param_for(value_key, value_value, value_parent)
|
113
|
+
end
|
114
|
+
params
|
115
|
+
else
|
116
|
+
["#{parent ? parent + "[#{key}]" : key.to_s}=#{CGI::escape(value.to_s)}"]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def handle_error(request, response)
|
121
|
+
return unless error = Projector::ERROR_MAP[response.code.to_i]
|
122
|
+
message = nil
|
123
|
+
begin
|
124
|
+
message = JSON.parse(response.body) || request.path
|
125
|
+
rescue JSON::ParserError => e
|
126
|
+
end
|
127
|
+
# Raise error
|
128
|
+
err = error.new(message)
|
129
|
+
err.retry_after = response['retry-after'].to_i if response.code.to_i >= 500
|
130
|
+
err.request = request
|
131
|
+
err.response = response
|
132
|
+
err.response_body = message
|
133
|
+
err.request_id = request['request-id']
|
134
|
+
raise err
|
135
|
+
end
|
136
|
+
|
137
|
+
def json_request(*args)
|
138
|
+
# Perform request, pass result format
|
139
|
+
Response.new(request(*args))
|
140
|
+
end
|
141
|
+
|
142
|
+
def boolean_from_response(*args)
|
143
|
+
response = request(*args)
|
144
|
+
(200..299).include? response.code.to_i
|
145
|
+
end
|
146
|
+
|
147
|
+
def can_post_data?(method)
|
148
|
+
[:post, :put, :patch].include?(method)
|
149
|
+
end
|
150
|
+
|
151
|
+
def log_debug_data(*args)
|
152
|
+
return unless ENV['PROJECTOR_DEBUG_HTTP']
|
153
|
+
@logger ||= begin
|
154
|
+
require 'logger'
|
155
|
+
Logger.new(STDOUT)
|
156
|
+
end
|
157
|
+
@logger.info(args.reject{|s| s = s.to_s; s.nil? || s.empty?}.join(' - '))
|
158
|
+
end
|
159
|
+
|
160
|
+
# Response class responsible for deserializing API calls
|
161
|
+
# @attr [Object] body The parsed response
|
162
|
+
# @attr [Hash] headers HTTP headers returned as part of the response
|
163
|
+
# @attr [String] code The HTTP response code
|
164
|
+
class Response
|
165
|
+
attr_accessor :body, :headers, :code
|
166
|
+
|
167
|
+
# Initializes a new result
|
168
|
+
#
|
169
|
+
# @param http_response [Net::HTTPResponse] the raw response to parse
|
170
|
+
def initialize(http_response)
|
171
|
+
@code = http_response.code
|
172
|
+
parse_headers(http_response.to_hash)
|
173
|
+
parse_body(http_response.body)
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
# Parses raw headers in to a reasonable hash
|
179
|
+
def parse_headers(raw_headers)
|
180
|
+
# raw headers from net_http have an array for each result. Flatten them out.
|
181
|
+
@headers = raw_headers.inject({}) do |remainder, (k, v)|
|
182
|
+
remainder[k] = [v].flatten.first
|
183
|
+
remainder
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Parses the raw body in to a hash
|
188
|
+
def parse_body(response_body)
|
189
|
+
# Parse JSON
|
190
|
+
begin
|
191
|
+
self.body = JSON.parse(response_body)
|
192
|
+
rescue JSON::ParserError => ex
|
193
|
+
self.body = response_body
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class TooManyRedirectsError < StandardError
|
199
|
+
attr_accessor :request, :response, :redirect_count
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
data/lib/projector/version.rb
CHANGED
data/lib/projector.rb
CHANGED
@@ -1,7 +1,57 @@
|
|
1
|
-
require 'projector/repo'
|
2
|
-
require 'projector/helpers'
|
3
|
-
require 'projector/cli'
|
4
1
|
require 'projector/version'
|
2
|
+
require 'projector/client'
|
5
3
|
|
6
4
|
module Projector
|
7
|
-
|
5
|
+
class << self
|
6
|
+
# Alias for Projector::Client.new
|
7
|
+
#
|
8
|
+
# @return [Projector::Client]
|
9
|
+
def new(options = {})
|
10
|
+
Projector::Client.new(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Delegate to Projector::Client.new
|
14
|
+
def method_missing(method, *args, &block)
|
15
|
+
return super unless new.respond_to?(method)
|
16
|
+
new.send(method, *args, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Forward respond_to? to Projector::Client.new
|
20
|
+
def respond_to?(method, include_private = false)
|
21
|
+
new.respond_to?(method, include_private) || super(method, include_private)
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
@logger ||= begin
|
26
|
+
if defined?(::Rails)
|
27
|
+
Rails.logger
|
28
|
+
else
|
29
|
+
require 'logger'
|
30
|
+
::Logger.new(STDOUT)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def logger=(x)
|
36
|
+
@logger = x
|
37
|
+
end
|
38
|
+
|
39
|
+
DEFAULT_ERROR_HANDLER = Proc.new do |ex, ctx|
|
40
|
+
Projector.logger.error(ex)
|
41
|
+
Projector.logger.error(ctx.inspect)
|
42
|
+
ex.backtrace.each do |line|
|
43
|
+
Projector.logger.error(line)
|
44
|
+
end if ex.backtrace
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Add a global error handler for any async errors Projector encounters.
|
49
|
+
# Error handlers must respond to `call(exception, context_hash)`:
|
50
|
+
#
|
51
|
+
# Projector.error_handlers << Proc.new {|ex, ctx| do_something(ex) }
|
52
|
+
#
|
53
|
+
def error_handlers
|
54
|
+
@handlers ||= [DEFAULT_ERROR_HANDLER]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/projector.gemspec
CHANGED
@@ -1,22 +1,21 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'projector/version'
|
4
5
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
s.email = ["jay@zeschin.org"]
|
11
|
-
s.homepage = "https://github.com/jayzes/projector"
|
12
|
-
s.summary = %q{Automatically keep a working directory with checkouts of all your Github projects}
|
13
|
-
s.description = %q{Automatically keep a working directory with checkouts of all your Github projects}
|
14
|
-
|
15
|
-
s.add_dependency 'json'
|
16
|
-
s.add_dependency 'thor'
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "projector"
|
8
|
+
spec.version = Projector::VERSION
|
9
|
+
spec.authors = ["Projector"]
|
10
|
+
spec.email = ["engineering@projector.com"]
|
17
11
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
spec.summary = %q{Ruby SDK for interacting with the Projector SDK}
|
13
|
+
spec.description = %q{Create and manage users, devices, and events going to Projector.}
|
14
|
+
spec.homepage = "https://www.projector.com"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
spec.license = "BSD-3-Clause"
|
19
|
+
|
20
|
+
spec.add_dependency 'net-http-persistent', '~> 2.9', '>= 2.9.4'
|
22
21
|
end
|
data/sample/Gemfile
ADDED
data/sample/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
remote: https://gem.fury.io/projector/
|
4
|
+
specs:
|
5
|
+
backports (3.6.8)
|
6
|
+
multi_json (1.11.2)
|
7
|
+
net-http-persistent (2.9.4)
|
8
|
+
projector (1.0.0)
|
9
|
+
net-http-persistent (~> 2.9, >= 2.9.4)
|
10
|
+
rack (1.6.4)
|
11
|
+
rack-protection (1.5.3)
|
12
|
+
rack
|
13
|
+
rack-test (0.6.3)
|
14
|
+
rack (>= 1.0)
|
15
|
+
sinatra (1.4.7)
|
16
|
+
rack (~> 1.5)
|
17
|
+
rack-protection (~> 1.4)
|
18
|
+
tilt (>= 1.3, < 3)
|
19
|
+
sinatra-contrib (1.4.6)
|
20
|
+
backports (>= 2.0)
|
21
|
+
multi_json
|
22
|
+
rack-protection
|
23
|
+
rack-test
|
24
|
+
sinatra (~> 1.4.0)
|
25
|
+
tilt (>= 1.3, < 3)
|
26
|
+
tilt (2.0.2)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
projector!
|
33
|
+
sinatra
|
34
|
+
sinatra-contrib
|
35
|
+
|
36
|
+
BUNDLED WITH
|
37
|
+
1.11.2
|
data/sample/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Projector Ruby SDK - Sample App Implementation
|
2
|
+
|
3
|
+
This directory houses a sample Sinatra implementation app using the Projector Ruby SDK.
|
4
|
+
|
5
|
+
Be sure to grab your Projector token from the dashboard and update the `config.yml` with it.
|
6
|
+
|
7
|
+
You should be able to run the application and hit the endpoints in `index.rb` with the following commands.
|
8
|
+
|
9
|
+
```sh
|
10
|
+
> (cd ../; gem build projector.gemspec)
|
11
|
+
> bundle install
|
12
|
+
> bundle exec rackup -p 8080
|
13
|
+
```
|
14
|
+
|
15
|
+
The application is configured to automatically send test events on a scheduled timer every 10 s.
|
16
|
+
|
17
|
+
You should see logging in the session std out.
|
18
|
+
|
19
|
+
## Sample curl
|
20
|
+
|
21
|
+
```sh
|
22
|
+
# In order to test the registration flow:
|
23
|
+
curl -XPOST localhost:8080/register_projector -d ''
|
24
|
+
|
25
|
+
# In order to send a tag-targeted event
|
26
|
+
curl -XPOST localhost:8080/cheeseburger_alert -d ''
|
27
|
+
|
28
|
+
# In order to send an end user-targeted event
|
29
|
+
curl -XPOST localhost:8080/alert_jane -d ''
|
30
|
+
```
|
data/sample/config.ru
ADDED
data/sample/config.yml
ADDED
data/sample/index.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'sinatra/config_file'
|
3
|
+
require 'projector'
|
4
|
+
require 'sinatra/json'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
class Sample < Sinatra::Base
|
8
|
+
|
9
|
+
register Sinatra::ConfigFile
|
10
|
+
|
11
|
+
config_file './config.yml'
|
12
|
+
|
13
|
+
Projector::Client.configure do |client|
|
14
|
+
client[:host] = settings.host
|
15
|
+
client[:token] = settings.token
|
16
|
+
client
|
17
|
+
end
|
18
|
+
|
19
|
+
# Forward an end user registration, i.e. from a mobile client device,
|
20
|
+
# to projector's register end user endpoint.
|
21
|
+
# Then, return the response data back to the requesting device.
|
22
|
+
# Here we're specifying two tags which Jane is part of.
|
23
|
+
post '/register_projector' do
|
24
|
+
# Implementer TODO: Handle authentication
|
25
|
+
# Extract user id and tags and insert into the
|
26
|
+
# end user object like below:
|
27
|
+
res = Projector.register_end_user({id: 'Jane', tags: ['likes dogs', 'eats_cheeseburgers']})
|
28
|
+
json res
|
29
|
+
end
|
30
|
+
|
31
|
+
# Deliver an event to the tags group called eats_cheeseburgers.
|
32
|
+
# Jane would receive this notification, as she's part of the eats_cheeseburgers
|
33
|
+
# tag set. Use the cheeseburger_alert event key.
|
34
|
+
post '/cheeseburger_alert' do
|
35
|
+
res = Projector.deliver_event({
|
36
|
+
target: {
|
37
|
+
tags: ['eats_cheeseburgers']
|
38
|
+
},
|
39
|
+
event_context: {
|
40
|
+
discount_price: 12,
|
41
|
+
restaurant_name: "Bill's Cheeseburgerz"
|
42
|
+
},
|
43
|
+
event_key: 'cheeseburger_alert'
|
44
|
+
})
|
45
|
+
json res
|
46
|
+
end
|
47
|
+
|
48
|
+
# Deliver an event specifically to Jane for the event_key cheeseburger_alert.
|
49
|
+
post '/alert_jane' do
|
50
|
+
res = Projector.deliver_event({
|
51
|
+
target: {
|
52
|
+
end_users: [{id: 'Jane'}]
|
53
|
+
},
|
54
|
+
event_context: {
|
55
|
+
discount_price: 12,
|
56
|
+
restaurant_name: "Bill's Cheeseburgerz"
|
57
|
+
},
|
58
|
+
event_key: 'cheeseburger_alert'
|
59
|
+
})
|
60
|
+
json res
|
61
|
+
end
|
62
|
+
|
63
|
+
# Example Application Logic:
|
64
|
+
# Our application delivers random discounts to
|
65
|
+
# registered end users on a timer using the Projector Node SDK.
|
66
|
+
@discounts = [
|
67
|
+
{
|
68
|
+
discount_price: 12,
|
69
|
+
restaurant_name: "Bill's Cheeseburgerz"
|
70
|
+
},
|
71
|
+
{
|
72
|
+
discount_price: 11,
|
73
|
+
restaurant_name: "Ted's Cheeseburger Shack"
|
74
|
+
},
|
75
|
+
{
|
76
|
+
discount_price: 14,
|
77
|
+
restaurant_name: "Joan of Arc's Cheesesteaks"
|
78
|
+
}
|
79
|
+
]
|
80
|
+
|
81
|
+
# An example of custom logic which sends cheeseburger discounts
|
82
|
+
# to registered end users on a timer schedule.
|
83
|
+
Thread.new do
|
84
|
+
loop do
|
85
|
+
begin
|
86
|
+
disc = @discounts.sample
|
87
|
+
res = Projector.deliver_event({
|
88
|
+
target: {
|
89
|
+
tags: ['eats_cheeseburgers']
|
90
|
+
},
|
91
|
+
event_context: disc,
|
92
|
+
event_key: 'cheeseburger_alert'
|
93
|
+
})
|
94
|
+
puts "Successfully sent cheeseburger_alert #{res}"
|
95
|
+
rescue Error => e
|
96
|
+
puts e
|
97
|
+
end
|
98
|
+
sleep 10
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
metadata
CHANGED
@@ -1,105 +1,88 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: projector
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease: false
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 0
|
10
|
-
version: 0.1.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
13
|
-
-
|
6
|
+
authors:
|
7
|
+
- Projector
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
requirements:
|
11
|
+
date: 2016-03-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-http-persistent
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.9'
|
27
20
|
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
version: "0"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.9.4
|
33
23
|
type: :runtime
|
34
|
-
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: thor
|
37
24
|
prerelease: false
|
38
|
-
|
39
|
-
|
40
|
-
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.9'
|
41
30
|
- - ">="
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
version_requirements: *id002
|
49
|
-
description: Automatically keep a working directory with checkouts of all your Github projects
|
50
|
-
email:
|
51
|
-
- jay@zeschin.org
|
52
|
-
executables:
|
53
|
-
- projector
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.9.4
|
33
|
+
description: Create and manage users, devices, and events going to Projector.
|
34
|
+
email:
|
35
|
+
- engineering@projector.com
|
36
|
+
executables: []
|
54
37
|
extensions: []
|
55
|
-
|
56
38
|
extra_rdoc_files: []
|
57
|
-
|
58
|
-
|
59
|
-
- .gitignore
|
39
|
+
files:
|
40
|
+
- ".gitignore"
|
60
41
|
- Gemfile
|
42
|
+
- LICENSE
|
61
43
|
- README.md
|
62
44
|
- Rakefile
|
63
|
-
- bin/
|
45
|
+
- bin/console
|
46
|
+
- bin/setup
|
47
|
+
- circle.yml
|
64
48
|
- lib/projector.rb
|
65
|
-
- lib/projector/
|
66
|
-
- lib/projector/
|
67
|
-
- lib/projector/
|
49
|
+
- lib/projector/api_methods.rb
|
50
|
+
- lib/projector/client.rb
|
51
|
+
- lib/projector/end_user.rb
|
52
|
+
- lib/projector/envelope.rb
|
53
|
+
- lib/projector/error.rb
|
54
|
+
- lib/projector/event.rb
|
55
|
+
- lib/projector/transport/http.rb
|
68
56
|
- lib/projector/version.rb
|
69
57
|
- projector.gemspec
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
58
|
+
- sample/Gemfile
|
59
|
+
- sample/Gemfile.lock
|
60
|
+
- sample/README.md
|
61
|
+
- sample/config.ru
|
62
|
+
- sample/config.yml
|
63
|
+
- sample/index.rb
|
64
|
+
homepage: https://www.projector.com
|
65
|
+
licenses:
|
66
|
+
- BSD-3-Clause
|
67
|
+
metadata: {}
|
74
68
|
post_install_message:
|
75
69
|
rdoc_options: []
|
76
|
-
|
77
|
-
require_paths:
|
70
|
+
require_paths:
|
78
71
|
- lib
|
79
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
-
|
81
|
-
requirements:
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
82
74
|
- - ">="
|
83
|
-
- !ruby/object:Gem::Version
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
version: "0"
|
88
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
|
-
requirements:
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
91
79
|
- - ">="
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
|
94
|
-
segments:
|
95
|
-
- 0
|
96
|
-
version: "0"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
97
82
|
requirements: []
|
98
|
-
|
99
83
|
rubyforge_project:
|
100
|
-
rubygems_version:
|
84
|
+
rubygems_version: 2.5.1
|
101
85
|
signing_key:
|
102
|
-
specification_version:
|
103
|
-
summary:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Ruby SDK for interacting with the Projector SDK
|
104
88
|
test_files: []
|
105
|
-
|
data/bin/projector
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# -*- mode: ruby -*-
|
3
|
-
|
4
|
-
# Requires
|
5
|
-
begin
|
6
|
-
require 'projector'
|
7
|
-
rescue LoadError
|
8
|
-
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
9
|
-
require 'rubygems'
|
10
|
-
require 'projector'
|
11
|
-
end
|
12
|
-
|
13
|
-
# Setup Constants
|
14
|
-
Projector::GEM_ROOT = File.expand_path( File.dirname( __FILE__ ) + '/../' )
|
15
|
-
|
16
|
-
# Run
|
17
|
-
Projector::CLI.start
|
data/lib/projector/cli.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
require 'thor'
|
2
|
-
require 'thor/actions'
|
3
|
-
|
4
|
-
module Projector
|
5
|
-
|
6
|
-
class CLI < Thor
|
7
|
-
|
8
|
-
# Includes
|
9
|
-
include Thor::Actions
|
10
|
-
include Projector::Helpers
|
11
|
-
|
12
|
-
# Tasks
|
13
|
-
desc "check", "Check Git settings to make sure Projector can work its magic properly"
|
14
|
-
long_desc <<-DESC
|
15
|
-
Projector will check to make sure that your Git settings contain your Github username
|
16
|
-
and API key (see http://help.github.com/set-your-user-name-email-and-github-token/)
|
17
|
-
as well as your preferred working directory
|
18
|
-
DESC
|
19
|
-
def check
|
20
|
-
unless github_username?
|
21
|
-
say 'You need to configure your github.user setting - "git config --global github.user <username>" should do the trick'
|
22
|
-
exit 1
|
23
|
-
end
|
24
|
-
unless github_token?
|
25
|
-
say 'You need to configure your github.token setting - "git config --global github.token <token>" should do the trick'
|
26
|
-
exit 1
|
27
|
-
end
|
28
|
-
unless projector_working_dir?
|
29
|
-
say 'You need to configure your projector.workingdir setting - "git config --global projector.workingdir <directory>" should do the trick'
|
30
|
-
exit 1
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
|
-
desc "checkout", "Go through the Github repositories you can access and ensure that working copies for them are cloned into your working directory"
|
36
|
-
long_desc <<-DESC
|
37
|
-
Projector loops through all of the repositories that you can access and clones working
|
38
|
-
copies of them under your configured working directory if they don't already exist
|
39
|
-
DESC
|
40
|
-
method_option :assume, :type => :boolean, :aliases => "-a"
|
41
|
-
def checkout
|
42
|
-
invoke :check
|
43
|
-
repos = all_repos
|
44
|
-
say "Checking #{repos.size} repositories"
|
45
|
-
repos.each do |repo|
|
46
|
-
say "Looking at #{repo.path}"
|
47
|
-
if should_clone_repo?(repo, options[:assume])
|
48
|
-
say "Cloning #{repo.clone_url} to #{repo.clone_path(projector_working_dir)}"
|
49
|
-
repo.clone(projector_working_dir)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
desc "update", "Go through the Github repositories you have in your working copy and see which ones need updating from origin"
|
55
|
-
long_desc <<-DESC
|
56
|
-
Projector loops through all of the repositories that you can access and updates the working
|
57
|
-
copies of them under your configured working directory if they are outdated
|
58
|
-
DESC
|
59
|
-
method_option :assume, :type => :boolean, :aliases => "-a"
|
60
|
-
def update
|
61
|
-
invoke :check
|
62
|
-
repos = all_repos
|
63
|
-
say "Checking #{repos.size} repositories"
|
64
|
-
repos.each do |repo|
|
65
|
-
unless repo_cloned?(repo)
|
66
|
-
say "Skipping #{repo.path} since it is not cloned"
|
67
|
-
next
|
68
|
-
end
|
69
|
-
say "Looking at #{repo.path}"
|
70
|
-
update_repo(repo) if repo_out_of_date?(repo) && (yes?("Repo #{repo.path} has remote changes. Update it? (y/n)") || options[:assume])
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
desc "version", "Prints Projector's version information"
|
79
|
-
def version
|
80
|
-
say "Projector version #{Projector::VERSION}"
|
81
|
-
end
|
82
|
-
map %w(-v --version) => :version
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
data/lib/projector/helpers.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
require "net/https"
|
2
|
-
require "uri"
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Projector
|
6
|
-
module Helpers
|
7
|
-
def github_username
|
8
|
-
`git config --get github.user`.strip
|
9
|
-
end
|
10
|
-
|
11
|
-
def github_username?
|
12
|
-
github_username && github_username.length > 0
|
13
|
-
end
|
14
|
-
|
15
|
-
def github_token
|
16
|
-
`git config --get github.token`.strip
|
17
|
-
end
|
18
|
-
|
19
|
-
def github_token?
|
20
|
-
github_token && github_token.length > 0
|
21
|
-
end
|
22
|
-
|
23
|
-
def projector_working_dir
|
24
|
-
`git config --get projector.workingdir`.strip
|
25
|
-
end
|
26
|
-
|
27
|
-
def projector_working_dir?
|
28
|
-
projector_working_dir && projector_working_dir.length > 0
|
29
|
-
end
|
30
|
-
|
31
|
-
def repo_cloned?(repo)
|
32
|
-
File.directory?(File.join(repo.clone_path(projector_working_dir), '.git'))
|
33
|
-
end
|
34
|
-
|
35
|
-
def repo_out_of_date?(repo)
|
36
|
-
repo.fetch_refs(projector_working_dir)
|
37
|
-
repo.local_head(projector_working_dir) != repo.remote_head(projector_working_dir)
|
38
|
-
end
|
39
|
-
|
40
|
-
def update_repo(repo)
|
41
|
-
repo.update(projector_working_dir)
|
42
|
-
end
|
43
|
-
|
44
|
-
def should_clone_repo?(repo, assume_yes = false)
|
45
|
-
!repo_cloned?(repo) && (assume_yes || yes?("Repo #{repo.path} is not cloned yet. Clone it? (y/n)"))
|
46
|
-
end
|
47
|
-
|
48
|
-
def user_repos
|
49
|
-
repo_api_request("https://github.com/api/v2/json/repos/show/#{github_username}")
|
50
|
-
end
|
51
|
-
|
52
|
-
def organization_repos
|
53
|
-
repo_api_request("https://github.com/api/v2/json/organizations/repositories?owned=1")
|
54
|
-
end
|
55
|
-
|
56
|
-
def collaborator_repos
|
57
|
-
repo_api_request("https://github.com/api/v2/json/repos/pushable")
|
58
|
-
end
|
59
|
-
|
60
|
-
def all_repos
|
61
|
-
(user_repos + organization_repos + collaborator_repos).uniq
|
62
|
-
end
|
63
|
-
|
64
|
-
def repo_api_request(endpoint)
|
65
|
-
uri = URI.parse(endpoint)
|
66
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
67
|
-
http.use_ssl = true
|
68
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # TODO: http://www.rubyinside.com/how-to-cure-nethttps-risky-default-https-behavior-4010.html
|
69
|
-
request = Net::HTTP::Get.new(uri.request_uri)
|
70
|
-
request.basic_auth("#{github_username}/token", github_token)
|
71
|
-
response = http.request(request)
|
72
|
-
JSON.parse(response.body)['repositories'].map { |repo| Repo.new(repo['owner'], repo['name']) }
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
data/lib/projector/repo.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
Repo = Struct.new(:owner, :name) do
|
2
|
-
def path
|
3
|
-
"#{self.owner}/#{self.name}"
|
4
|
-
end
|
5
|
-
|
6
|
-
def clone_path(base_dir)
|
7
|
-
File.join(base_dir, self.owner, self.name)
|
8
|
-
end
|
9
|
-
|
10
|
-
def clone_url
|
11
|
-
"git@github.com:#{self.owner}/#{self.name}.git"
|
12
|
-
end
|
13
|
-
|
14
|
-
def clone(base_dir)
|
15
|
-
system("git clone #{clone_url} #{clone_path(base_dir)}")
|
16
|
-
end
|
17
|
-
|
18
|
-
def local_head(base_dir)
|
19
|
-
`cd #{clone_path(base_dir)} && git rev-parse master`.strip
|
20
|
-
end
|
21
|
-
|
22
|
-
def remote_head(base_dir)
|
23
|
-
`cd #{clone_path(base_dir)} && git rev-parse origin/master`.strip
|
24
|
-
end
|
25
|
-
|
26
|
-
def fetch_refs(base_dir)
|
27
|
-
system("cd #{clone_path(base_dir)} && git fetch origin")
|
28
|
-
end
|
29
|
-
|
30
|
-
def update(base_dir)
|
31
|
-
system("cd #{clone_path(base_dir)} && git pull --rebase origin master")
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|