strobe 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/bin/strobe +2 -12
  2. data/lib/strobe.rb +35 -88
  3. data/lib/strobe/association.rb +86 -0
  4. data/lib/strobe/cli.rb +39 -53
  5. data/lib/strobe/cli/action.rb +131 -0
  6. data/lib/strobe/cli/actions.rb +103 -0
  7. data/lib/strobe/cli/settings.rb +62 -0
  8. data/lib/strobe/collection.rb +53 -0
  9. data/lib/strobe/connection.rb +91 -0
  10. data/lib/strobe/resource/base.rb +235 -0
  11. data/lib/strobe/resource/collection.rb +50 -0
  12. data/lib/strobe/resource/singleton.rb +18 -0
  13. data/lib/strobe/resources/account.rb +12 -0
  14. data/lib/strobe/resources/application.rb +129 -0
  15. data/lib/strobe/resources/me.rb +11 -0
  16. data/lib/strobe/resources/signup.rb +27 -0
  17. data/lib/strobe/resources/team.rb +13 -0
  18. data/lib/strobe/resources/user.rb +11 -0
  19. data/lib/strobe/validations.rb +118 -0
  20. metadata +50 -124
  21. data/.gitignore +0 -5
  22. data/Gemfile +0 -4
  23. data/Gemfile.lock +0 -69
  24. data/Rakefile +0 -36
  25. data/features/1_sign_up.feature +0 -16
  26. data/features/2_deploying.feature +0 -35
  27. data/features/3_deploying_sproutcore_apps.feature +0 -19
  28. data/features/step_definitions/cli_steps.rb +0 -8
  29. data/features/step_definitions/deploying_steps.rb +0 -58
  30. data/features/step_definitions/sign_up.rb +0 -74
  31. data/features/support/helpers.rb +0 -104
  32. data/features/support/strobe.rb +0 -57
  33. data/lib/strobe/account.rb +0 -67
  34. data/lib/strobe/deploy.rb +0 -131
  35. data/lib/strobe/errors.rb +0 -12
  36. data/lib/strobe/http.rb +0 -94
  37. data/lib/strobe/server.rb +0 -13
  38. data/lib/strobe/settings.rb +0 -56
  39. data/lib/strobe/ui.rb +0 -81
  40. data/lib/strobe/user.rb +0 -9
  41. data/lib/strobe/version.rb +0 -3
  42. data/strobe.gemspec +0 -35
@@ -0,0 +1,103 @@
1
+ module Strobe
2
+ module CLI::Actions
3
+ class SignupAction < CLI::Action
4
+ def steps
5
+ resource Signup.new :account => { :name => 'default' }
6
+
7
+ if settings[:token]
8
+ say "This computer is already registered with a Strobe account."
9
+ unless agree "Signup anyway? [Yn] "
10
+ say "exiting..."
11
+ exit
12
+ end
13
+ end
14
+
15
+ say "Please fill out the following information."
16
+
17
+ ask :invitation
18
+ ask :email, :into => "user.email"
19
+
20
+ group :validate => "user.password" do
21
+ password :password, :into => "user.password"
22
+ password :password_confirmation, :into => "password_confirmation"
23
+ end
24
+
25
+ save :on_error => :restart do
26
+ settings[:token] = resource['user.authentication_token']
27
+ settings.authenticate!
28
+ say "Your account was created successfully."
29
+ end
30
+ end
31
+ end
32
+
33
+ class LoginAction < CLI::Action
34
+ def steps
35
+ resource Me.new
36
+
37
+ say "Please enter your Strobe username and password."
38
+
39
+ ask :email, :into => "user.email"
40
+ password :password, :into => "user.password"
41
+
42
+ save :on_error => :restart do
43
+ settings[:token] = resource['user.authentication_token']
44
+ settings.authenticate!
45
+ say "The computer has been registered with the account."
46
+ end
47
+ end
48
+ end
49
+
50
+ class DeployAction < CLI::Action
51
+ def steps
52
+ ensure_computer_is_registered
53
+
54
+
55
+ config = options[:config]
56
+ path = determine_path
57
+
58
+ if application_id = config[:application_id]
59
+ resource Application.get application_id, :lazy => true
60
+ else
61
+ resource Account.first.applications.new
62
+ end
63
+
64
+ resource[:path] = path
65
+
66
+ unless resource.persisted?
67
+ say "Please fill out the following information."
68
+
69
+ ask :name
70
+ ask :url
71
+ end
72
+
73
+ save do
74
+ config[:application_id] ||= resource[:id]
75
+ say "The application has successfully been deployed and is available at #{resource[:url]}"
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def determine_path
82
+ path = options[:path]
83
+
84
+ if File.exist?("#{path}/tmp/build")
85
+ return "#{path}/tmp/build"
86
+ end
87
+
88
+ path
89
+ end
90
+
91
+ def ensure_computer_is_registered
92
+ unless settings[:token]
93
+ say "This computer is not yet registered with a Strobe account."
94
+
95
+ choose do |m|
96
+ m.choice(:login) { run :login }
97
+ m.choice(:signup) { run :signup }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,62 @@
1
+ require 'yaml'
2
+
3
+ module Strobe
4
+ class CLI::Settings
5
+ def self.global_config_file
6
+ Pathname.new(Strobe.user_home).join(".strobe/config")
7
+ end
8
+
9
+ def self.application_config_file(root)
10
+ Pathname.new(root).join(".strobe/config")
11
+ end
12
+
13
+ def initialize(filename = self.class.global_config_file)
14
+ @filename = filename
15
+
16
+ if @filename.exist?
17
+ @hash = YAML.load(@filename)
18
+ else
19
+ @hash = {}
20
+ end
21
+ end
22
+
23
+ def [](k)
24
+ ENV[key(k)] || @hash[key(k)]
25
+ end
26
+
27
+ def []=(k, val)
28
+ @hash[key(k)] = val
29
+
30
+ # Persist the setting
31
+ FileUtils.mkdir_p(@filename.dirname)
32
+ File.open(@filename, "wb") do |file|
33
+ file.puts @hash.to_yaml
34
+ end
35
+
36
+ val
37
+ end
38
+
39
+ def connection
40
+ return @connection if @connection
41
+ host = self[:url] || "http://api.strobeapp.com/"
42
+ @connection = Connection.new(host)
43
+ end
44
+
45
+ def connect!
46
+ Strobe.connection = connection
47
+ authenticate!
48
+ end
49
+
50
+ def authenticate!
51
+ if token = self[:token]
52
+ connection.authenticate_with_token(token)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def key(key)
59
+ "STROBE_#{key}".upcase
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,53 @@
1
+ module Strobe
2
+ class Collection
3
+ include Enumerable
4
+
5
+ attr_reader :klass, :params
6
+
7
+ def initialize(klass, params = {})
8
+ @klass, @params = klass, params
9
+ end
10
+
11
+ def to_a
12
+ @to_a ||= begin
13
+ resp = Strobe.connection.get klass.resource_uri, params
14
+
15
+ if resp.status == 200
16
+ resp.body[klass.resource_name].map do |hash|
17
+ klass.new(hash)
18
+ end
19
+ else
20
+ raise "Something went wrong"
21
+ end
22
+ end
23
+ end
24
+
25
+ def each
26
+ to_a.each { |r| yield r }
27
+ end
28
+
29
+ def all
30
+ self
31
+ end
32
+
33
+ def [](index)
34
+ to_a[index]
35
+ end
36
+
37
+ def length
38
+ to_a.length
39
+ end
40
+
41
+ def new(params = {})
42
+ klass.new(@params.merge(params))
43
+ end
44
+
45
+ def ==(other)
46
+ if Array === other
47
+ to_a === other
48
+ else
49
+ super
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,91 @@
1
+ require 'base64'
2
+ require 'faraday'
3
+
4
+ module Strobe
5
+ class Connection
6
+ attr_reader :url
7
+
8
+ def initialize(url)
9
+ @url = url
10
+ @user = nil
11
+ @password = nil
12
+ end
13
+
14
+ def authenticate_with_token(token)
15
+ authenticate_with_user_and_password(token, '')
16
+ end
17
+
18
+ def authenticate_with_user_and_password(user, password)
19
+ @json_connection = nil
20
+ @reg_connection = nil
21
+ @user, @password = user, password
22
+ end
23
+
24
+ def get(*args)
25
+ request :get, *args
26
+ end
27
+
28
+ def post(*args)
29
+ request :post, *args
30
+ end
31
+
32
+ def put(*args)
33
+ request :put, *args
34
+ end
35
+
36
+ def delete(*args)
37
+ request :delete, *args
38
+ end
39
+
40
+ def request(method, path, body = '', headers = {})
41
+ connection = case headers['Content-Type']
42
+ when /^multipart\/form-data;/ then reg_connection
43
+ else json_connection
44
+ end
45
+
46
+ resp = connection.run_request method, path, body, headers
47
+
48
+ case resp.status
49
+ when 401
50
+ # TODO Fix this on the server
51
+ raise UnauthenticatedError, resp.body['errors']
52
+ when 404
53
+ raise ResourceNotFoundError, resp.body['errors']
54
+ end
55
+
56
+ resp
57
+ end
58
+
59
+ private
60
+
61
+ def authorization_header
62
+ auth = Base64.encode64("#{@user}:#{@password}")
63
+ auth.gsub!("\n", "")
64
+
65
+ "Basic #{auth}"
66
+ end
67
+
68
+ def authorize?
69
+ @user
70
+ end
71
+
72
+ def json_connection
73
+ @json_connection ||= Faraday::Connection.new(:url => @url) do |b|
74
+ b.use Faraday::Request::ActiveSupportJson
75
+ b.use Faraday::Adapter::NetHttp
76
+ b.use Faraday::Response::ActiveSupportJson
77
+ b.options[:timeout] = 30
78
+ b.headers['authorization'] = authorization_header if authorize?
79
+ end
80
+ end
81
+
82
+ def reg_connection
83
+ @reg_connection ||= Faraday::Connection.new(:url => @url) do |b|
84
+ b.use Faraday::Adapter::NetHttp
85
+ b.use Faraday::Response::ActiveSupportJson
86
+ b.options[:timeout] = 30
87
+ b.headers['authorization'] = authorization_header if authorize?
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,235 @@
1
+ module Strobe
2
+ module Resource
3
+ class AssociationValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attr, val)
5
+ if inst = record.send(attr)
6
+ inst.valid?
7
+ inst.errors.each do |key, msg|
8
+ record.errors.add("#{attr}.#{key}", msg)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ module Base
15
+ extend ActiveSupport::Concern
16
+ include Validations
17
+
18
+ included do
19
+ extend ActiveModel::Callbacks
20
+ define_model_callbacks :save, :create, :update, :destroy
21
+ end
22
+
23
+ module ClassMethods
24
+ def n
25
+ :n
26
+ end
27
+
28
+ def has(cardinality, name, options = {})
29
+ assoc = Association.new(self, cardinality, name, options)
30
+
31
+ class_eval(assoc.reader)
32
+ class_eval(assoc.writer)
33
+
34
+ if assoc.include?
35
+ validates_with AssociationValidator, :attributes => [name]
36
+ end
37
+
38
+ associations[assoc.name] = assoc
39
+ end
40
+
41
+ def filter(*params)
42
+ @filter ||= []
43
+
44
+ return @filter if params.empty?
45
+
46
+ @filter |= params.map { |p| p.to_s }
47
+ end
48
+
49
+ def associations
50
+ @associations ||= {}
51
+ end
52
+
53
+ def base_const_name
54
+ name.gsub(/^Strobe::Resources::/, '')
55
+ end
56
+
57
+ def resource_name
58
+ raise NotImplementedError, 'Implemented in a subclass'
59
+ end
60
+
61
+ def singular_resource_name
62
+ raise NotImplementedError, 'Implemented in a subclass'
63
+ end
64
+
65
+ def uri_prefix
66
+ @uri_prefix ||= "/#{resource_name}"
67
+ end
68
+ end
69
+
70
+ attr_reader :response
71
+
72
+ def initialize(params = {})
73
+ @response = nil
74
+ self.params = params || {}
75
+ end
76
+
77
+ def persisted?
78
+ self[:id]
79
+ end
80
+
81
+ # TODO: Make this legit
82
+ def key?(key)
83
+ params.key?(key)
84
+ end
85
+
86
+ def params
87
+ @params.dup.tap do |params|
88
+ self.class.associations.each do |name, assoc|
89
+ next unless assoc.include? and inst = send(name)
90
+ params.deep_merge! name => inst.params
91
+ end
92
+ end
93
+ end
94
+
95
+ def params=(val)
96
+ params = with_indifferent_access(val)
97
+
98
+ extract_on_associations(params).each do |k, v|
99
+ send("#{k}=", v)
100
+ end
101
+
102
+ @params = params
103
+ val
104
+ end
105
+
106
+ def [](key)
107
+ if Array === key
108
+ parts = key
109
+ else
110
+ parts = key.to_s.split('.')
111
+ end
112
+
113
+ if self.class.associations.key?(parts.first.to_sym)
114
+ return unless inst = send(parts.first)
115
+ return inst if parts.length == 1
116
+ inst[parts[1..-1]]
117
+ else
118
+ parts.inject(params) do |param, key|
119
+ param[key]
120
+ end
121
+ end
122
+ end
123
+
124
+ def []=(key, val)
125
+ merge!(key.to_s.split('.').reverse.inject(val) do |param, key|
126
+ { key => param }
127
+ end)
128
+ end
129
+
130
+ def merge!(params)
131
+ params = with_indifferent_access(params || {})
132
+
133
+ extract_on_associations(params).each do |k, v|
134
+ unless inst = send(k)
135
+ inst = send("#{k}=", {})
136
+ end
137
+ inst.merge! v
138
+ end
139
+
140
+ @params.deep_merge! params
141
+ end
142
+
143
+ def persisted?
144
+ self[:id]
145
+ end
146
+
147
+ def save
148
+ if valid?
149
+ root = self.class.singular_resource_name
150
+ @response = self["id"] ? update : create
151
+
152
+ if @response.success?
153
+ merge! @response.body[root]
154
+ return true
155
+ elsif @response.status == 422
156
+ errors = @response.body['errors']
157
+ errors = errors[root] if errors
158
+ handle_errors(errors) if errors
159
+ else
160
+ raise "Something went wrong"
161
+ end
162
+
163
+ false
164
+ end
165
+ end
166
+
167
+ def destroy
168
+ raise NotImplemented
169
+ end
170
+
171
+ private
172
+
173
+ def connection
174
+ Strobe.connection
175
+ end
176
+
177
+ def http_verb
178
+ persisted? ? :put : :post
179
+ end
180
+
181
+ def http_uri
182
+ uri = self.class.uri_prefix
183
+ uri = "#{uri}/#{self[:id]}" if self[:id]
184
+ uri
185
+ end
186
+
187
+ def request_params
188
+ filter = self.class.filter
189
+ { self.class.singular_resource_name => params.except(*filter) }
190
+ end
191
+
192
+ def create
193
+ connection.post http_uri, request_params, {}
194
+ end
195
+
196
+ def update
197
+ connection.put http_uri, request_params, {}
198
+ end
199
+
200
+ def handle_errors(errors, key = nil)
201
+ errors.each do |k, v|
202
+ key = key ? "#{key}.#{k}" : k
203
+ case v
204
+ when Hash
205
+ handle_errors(v, key)
206
+ else
207
+ self.errors.add(key, v)
208
+ end
209
+ end
210
+ end
211
+
212
+ def with_indifferent_access(val)
213
+ case val
214
+ when Hash
215
+ h = HashWithIndifferentAccess.new
216
+ val.each { |k, v| h[k] = with_indifferent_access(v) }
217
+ h
218
+ when Array
219
+ val.map { |elem| with_indifferent_access(elem) }
220
+ else
221
+ val
222
+ end
223
+ end
224
+
225
+ def extract_on_associations(params)
226
+ {}.tap do |ret|
227
+ self.class.associations.keys.each do |key|
228
+ next unless params.key?(key)
229
+ ret[key] = params.delete(key)
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end