strobe 0.0.1 → 0.0.2

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