supso 0.10.0
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/DEV_README.md +28 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +8 -0
- data/README.md +25 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/bin/supso +6 -0
- data/docs/README.md +7 -0
- data/docs/commands/README.md +14 -0
- data/docs/commands/help.md +10 -0
- data/docs/commands/login.md +10 -0
- data/docs/commands/logout.md +7 -0
- data/docs/commands/show.md +21 -0
- data/docs/commands/update.md +7 -0
- data/docs/commands/version.md +12 -0
- data/docs/commands/whoami.md +5 -0
- data/docs/contents.yml +17 -0
- data/docs/errors/README.md +6 -0
- data/docs/errors/invalid_project_token.md +4 -0
- data/docs/errors/missing_project_token.md +4 -0
- data/docs/overview/README.md +16 -0
- data/docs/overview/getting_started.md +15 -0
- data/lib/helpers/module_vars.rb +19 -0
- data/lib/other/supso2.pub +9 -0
- data/lib/super_source/project.rb +9 -0
- data/lib/supso/commands.rb +155 -0
- data/lib/supso/config.rb +16 -0
- data/lib/supso/javascript.rb +57 -0
- data/lib/supso/logs.rb +49 -0
- data/lib/supso/organization.rb +95 -0
- data/lib/supso/project.rb +218 -0
- data/lib/supso/updater.rb +91 -0
- data/lib/supso/user.rb +161 -0
- data/lib/supso/util.rb +129 -0
- data/lib/supso/version.rb +8 -0
- data/lib/supso.rb +31 -0
- data/lib/templates/project_dir_readme.txt +44 -0
- data/lib/templates/user_dir_readme.txt +8 -0
- data/supso.gemspec +26 -0
- metadata +145 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Supso
|
4
|
+
class Organization
|
5
|
+
attr_accessor :name, :id
|
6
|
+
|
7
|
+
@@current_organization = nil
|
8
|
+
|
9
|
+
def initialize(name, id)
|
10
|
+
@name = name
|
11
|
+
@id = id
|
12
|
+
end
|
13
|
+
|
14
|
+
def save_to_file!
|
15
|
+
Util.ensure_path_exists!(Organization.current_organization_filename)
|
16
|
+
file = File.open(Organization.current_organization_filename, 'w')
|
17
|
+
file << self.saved_data.to_json
|
18
|
+
file.close
|
19
|
+
Project.save_project_directory_readme!
|
20
|
+
end
|
21
|
+
|
22
|
+
def saved_data
|
23
|
+
data = {}
|
24
|
+
data['name'] = self.name if self.name
|
25
|
+
data['id'] = self.id if self.id
|
26
|
+
data
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.current_organization_filename
|
30
|
+
"#{ Supso.project_supso_config_root }/current_organization.json"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.current_organization_from_file
|
34
|
+
organization_data = {}
|
35
|
+
begin
|
36
|
+
organization_data = JSON.parse(File.read(Organization.current_organization_filename))
|
37
|
+
organization_data = {} if !organization_data.is_a?(Object)
|
38
|
+
rescue
|
39
|
+
organization_data = {}
|
40
|
+
end
|
41
|
+
|
42
|
+
if organization_data['id']
|
43
|
+
Organization.new(organization_data['name'], organization_data['id'])
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.current_organization
|
50
|
+
@@current_organization ||= Organization.current_organization_from_file
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.current_organization_or_fetch
|
54
|
+
org = Organization.current_organization
|
55
|
+
|
56
|
+
if !org
|
57
|
+
Organization.fetch_current_organization!
|
58
|
+
org = Organization.current_organization
|
59
|
+
if !org
|
60
|
+
raise StandardError.new('Could not find current organization')
|
61
|
+
else
|
62
|
+
org
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.set_current_organization!(name, id)
|
68
|
+
@@current_organization = Organization.new(name, id)
|
69
|
+
@@current_organization.save_to_file!
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.delete_current_organization!
|
73
|
+
@@current_organization = nil
|
74
|
+
if File.exists?(Organization.current_organization_from_file)
|
75
|
+
File.delete(Organization.current_organization_from_file)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.fetch_current_organization!
|
80
|
+
user = User.current_user
|
81
|
+
data = {
|
82
|
+
auth_token: user.auth_token,
|
83
|
+
user_id: user.id,
|
84
|
+
}
|
85
|
+
response = Util.http_post("#{ Supso.supso_api_root }users/me/current_organization", data)
|
86
|
+
|
87
|
+
if response['success']
|
88
|
+
org = response['organization']
|
89
|
+
Organization.set_current_organization!(org['name'], org['id'])
|
90
|
+
else
|
91
|
+
puts response['reason']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'openssl'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module Supso
|
6
|
+
class Project
|
7
|
+
attr_accessor :name, :api_token, :client_data, :client_token, :source, :aliases
|
8
|
+
|
9
|
+
# Validities
|
10
|
+
MISSING_DATA = :missing_token
|
11
|
+
MISSING_TOKEN = :missing_token
|
12
|
+
DIFFERENT_API_TOKEN = :different_api_token
|
13
|
+
INVALID_TOKEN = :missing_token
|
14
|
+
MISSING_ORGANIZATION = :missing_organization
|
15
|
+
DIFFERENT_ORGANIZATION = :different_organization
|
16
|
+
VALID = :valid
|
17
|
+
|
18
|
+
def initialize(name, api_token, options = {})
|
19
|
+
@name = name
|
20
|
+
@api_token = api_token
|
21
|
+
@options = options
|
22
|
+
@client_data = self.load_client_data
|
23
|
+
@client_token = self.load_client_token
|
24
|
+
@source = options[:source] || options['source']
|
25
|
+
@aliases = options[:aliases] || options['aliases'] || []
|
26
|
+
end
|
27
|
+
|
28
|
+
def filename(filetype)
|
29
|
+
"#{ Supso.project_supso_config_root }/projects/#{ self.name }.#{ filetype }"
|
30
|
+
end
|
31
|
+
|
32
|
+
def data_filename
|
33
|
+
self.filename('json')
|
34
|
+
end
|
35
|
+
|
36
|
+
def identification_data
|
37
|
+
{
|
38
|
+
name: self.name,
|
39
|
+
api_token: self.api_token,
|
40
|
+
aliases: self.aliases,
|
41
|
+
source: self.source,
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def puts_info
|
46
|
+
puts "#{ self.name }"
|
47
|
+
|
48
|
+
if self.source
|
49
|
+
human_readable_source = self.source == 'add' ? 'add (ruby)' : self.source
|
50
|
+
puts " Source: #{ human_readable_source }"
|
51
|
+
end
|
52
|
+
|
53
|
+
if self.valid?
|
54
|
+
puts " Valid: Yes"
|
55
|
+
else
|
56
|
+
puts " Valid: No"
|
57
|
+
puts " Reason: #{ self.validity_explanation }"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_client_data
|
62
|
+
if File.exist?(self.data_filename)
|
63
|
+
JSON.parse(File.read(self.data_filename))
|
64
|
+
else
|
65
|
+
{}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def load_client_token
|
70
|
+
if self.token_file_exists?
|
71
|
+
File.read(self.token_filename)
|
72
|
+
else
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def token_filename
|
78
|
+
self.filename('token')
|
79
|
+
end
|
80
|
+
|
81
|
+
def token_file_exists?
|
82
|
+
File.exist?(self.token_filename)
|
83
|
+
end
|
84
|
+
|
85
|
+
def organization_id
|
86
|
+
self.client_data['organization_id']
|
87
|
+
end
|
88
|
+
|
89
|
+
def save_project_data!
|
90
|
+
if self.client_data.empty?
|
91
|
+
if File.exists?(self.data_filename)
|
92
|
+
File.delete(self.data_filename)
|
93
|
+
end
|
94
|
+
if File.exists?(self.token_filename)
|
95
|
+
File.delete(self.token_filename)
|
96
|
+
end
|
97
|
+
else
|
98
|
+
Project.save_project_directory_readme!
|
99
|
+
|
100
|
+
Util.ensure_path_exists!(self.data_filename)
|
101
|
+
file = File.open(self.data_filename, 'w')
|
102
|
+
file << self.client_data.to_json
|
103
|
+
file.close
|
104
|
+
|
105
|
+
Util.ensure_path_exists!(self.token_filename)
|
106
|
+
file = File.open(self.token_filename, 'w')
|
107
|
+
file << self.client_token
|
108
|
+
file.close
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def valid?
|
113
|
+
self.validity == VALID
|
114
|
+
end
|
115
|
+
|
116
|
+
def validity
|
117
|
+
if !self.client_token
|
118
|
+
return MISSING_TOKEN
|
119
|
+
end
|
120
|
+
|
121
|
+
if !self.client_data
|
122
|
+
return MISSING_DATA
|
123
|
+
end
|
124
|
+
|
125
|
+
if self.client_data['project_api_token'] != self.api_token
|
126
|
+
return DIFFERENT_API_TOKEN
|
127
|
+
end
|
128
|
+
|
129
|
+
if !Organization.current_organization
|
130
|
+
return MISSING_ORGANIZATION
|
131
|
+
end
|
132
|
+
|
133
|
+
if self.organization_id != Organization.current_organization.id
|
134
|
+
return DIFFERENT_ORGANIZATION
|
135
|
+
end
|
136
|
+
|
137
|
+
public_key = OpenSSL::PKey::RSA.new File.read("#{ Supso.gem_root }/lib/other/supso2.pub")
|
138
|
+
digest = OpenSSL::Digest::SHA256.new
|
139
|
+
|
140
|
+
if !public_key.verify(digest, Base64.decode64(self.client_token), self.client_data.to_json)
|
141
|
+
return INVALID_TOKEN
|
142
|
+
end
|
143
|
+
|
144
|
+
return VALID
|
145
|
+
end
|
146
|
+
|
147
|
+
def validity_explanation
|
148
|
+
case self.validity
|
149
|
+
when MISSING_TOKEN
|
150
|
+
"Missing client token. Run `supso update` to update the token."
|
151
|
+
when MISSING_DATA
|
152
|
+
"Missing client data. Run `supso update` to update the data."
|
153
|
+
when DIFFERENT_API_TOKEN
|
154
|
+
"Different api token. The project's api token is different from the project api token listed in your client data. Make sure your projects are all up-to-date and you have the latest client token from `supso update`."
|
155
|
+
when MISSING_ORGANIZATION
|
156
|
+
"Missing organization. Run `supso update` to update your organization file."
|
157
|
+
when DIFFERENT_ORGANIZATION
|
158
|
+
"Different organization. The client token uses organization id #{ organization_id }, but " +
|
159
|
+
"your current organization id is #{ Organization.current_organization['id'] } (#{ Organization.current_organization['name'] })."
|
160
|
+
when INVALID_TOKEN
|
161
|
+
"Invalid client token. Run `supso update` to update the token."
|
162
|
+
when VALID
|
163
|
+
"Valid."
|
164
|
+
else
|
165
|
+
"Invalid."
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def save_data!
|
170
|
+
self.save_project_data
|
171
|
+
end
|
172
|
+
|
173
|
+
class << self
|
174
|
+
attr_accessor :projects
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.projects
|
178
|
+
@projects ||= []
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.add(name, api_token, options = {})
|
182
|
+
# Correct for common mistakes:
|
183
|
+
options[:aliases] = options['aliases'] if options['aliases'] && !options[:aliases]
|
184
|
+
options[:source] = options['source'] if options['source'] && !options[:source]
|
185
|
+
|
186
|
+
options[:source] ||= 'add'
|
187
|
+
project = Project.new(name, api_token, options)
|
188
|
+
self.projects << project
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.detect_all_projects!
|
192
|
+
Util.require_all_gems!
|
193
|
+
Javascript.detect_all_projects!
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.save_project_directory_readme!
|
197
|
+
readme_path = "#{ Supso.project_supso_config_root }/README.txt"
|
198
|
+
if !File.exists?(readme_path)
|
199
|
+
readme_contents = File.open("#{ Supso.gem_root }/lib/templates/project_dir_readme.txt", 'r').read
|
200
|
+
Util.ensure_path_exists!(readme_path)
|
201
|
+
file = File.open(readme_path, 'w')
|
202
|
+
file << readme_contents
|
203
|
+
file.close
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.aliases_match?(aliases1 = [], aliases2 = [])
|
208
|
+
aliases2.each do |first_alias|
|
209
|
+
if aliases1.any? { |second_alias| second_alias['name'] == first_alias['name'] &&
|
210
|
+
second_alias['platform'] == first_alias['platform'] }
|
211
|
+
return true
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
false
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Supso
|
2
|
+
module Updater
|
3
|
+
def Updater.update
|
4
|
+
user = User.current_user
|
5
|
+
if user && user.auth_token
|
6
|
+
Updater.update_returning_user(user)
|
7
|
+
else
|
8
|
+
Updater.update_first_time_user
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def Updater.update_first_time_user
|
13
|
+
puts "Super Source lets you subscribe to projects, so that you can receive urgent security announcements, important new versions, and other information via email."
|
14
|
+
|
15
|
+
Project.detect_all_projects!
|
16
|
+
puts "You are using the following projects with Super Source:"
|
17
|
+
Project.projects.each do |project|
|
18
|
+
puts " #{ project.name }"
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "You can opt-out if you wish, however you must provide a valid email, in order to receive the confirmation token."
|
22
|
+
|
23
|
+
email = Commands.prompt_email
|
24
|
+
User.attach_to_email!(email)
|
25
|
+
|
26
|
+
succeeded = false
|
27
|
+
while !succeeded
|
28
|
+
token = Commands.prompt_confirmation_token(email)
|
29
|
+
succeeded, reason = User.log_in_with_confirmation_token!(email, token)
|
30
|
+
if !succeeded && reason
|
31
|
+
puts reason
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Updater.update_projects!
|
36
|
+
end
|
37
|
+
|
38
|
+
def Updater.update_returning_user(user)
|
39
|
+
org = Organization.current_organization_or_fetch
|
40
|
+
Project.detect_all_projects!
|
41
|
+
Updater.update_projects!
|
42
|
+
end
|
43
|
+
|
44
|
+
def Updater.update_projects!(projects = nil)
|
45
|
+
if projects.nil?
|
46
|
+
projects = Project.projects
|
47
|
+
end
|
48
|
+
|
49
|
+
if projects.length == 0
|
50
|
+
puts "No projects in list to update."
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
puts "Updating #{ Util.pluralize(projects.length, 'project') }..."
|
55
|
+
|
56
|
+
user = User.current_user
|
57
|
+
organization = Organization.current_organization
|
58
|
+
|
59
|
+
data = {
|
60
|
+
auth_token: user.auth_token,
|
61
|
+
user_id: user.id,
|
62
|
+
projects: projects.map { |project| project.identification_data },
|
63
|
+
}
|
64
|
+
|
65
|
+
response = Util.http_post("#{ Supso.supso_api_root }organizations/#{ organization.id }/client_tokens", data)
|
66
|
+
|
67
|
+
if response['success']
|
68
|
+
response['projects'].each do |project_response|
|
69
|
+
client_data = project_response['client_data']
|
70
|
+
client_token = project_response['client_token']
|
71
|
+
api_token = client_data['project_api_token']
|
72
|
+
aliases = client_data['project_aliases'] || []
|
73
|
+
project = projects.find { |find_project| find_project.api_token == api_token }
|
74
|
+
if project.nil?
|
75
|
+
project = projects.find { |find_project| Project.aliases_match?(find_project.aliases, aliases) }
|
76
|
+
|
77
|
+
if project.nil?
|
78
|
+
next # Could log warning
|
79
|
+
end
|
80
|
+
end
|
81
|
+
project.client_data = client_data
|
82
|
+
project.client_token = client_token
|
83
|
+
project.aliases = aliases
|
84
|
+
project.save_project_data!
|
85
|
+
end
|
86
|
+
else
|
87
|
+
puts response['reason']
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/supso/user.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Supso
|
4
|
+
class User
|
5
|
+
attr_accessor :email, :name, :id, :auth_token
|
6
|
+
|
7
|
+
@@current_user = nil
|
8
|
+
|
9
|
+
def initialize(email, name, id, auth_token = nil)
|
10
|
+
@email = email
|
11
|
+
@name = name
|
12
|
+
@id = id
|
13
|
+
@auth_token = auth_token
|
14
|
+
end
|
15
|
+
|
16
|
+
def save_to_file!
|
17
|
+
Util.ensure_path_exists!(User.current_user_filename)
|
18
|
+
file = File.open(User.current_user_filename, 'w')
|
19
|
+
file << self.saved_data.to_json
|
20
|
+
file.close
|
21
|
+
User.save_user_supso_readme!
|
22
|
+
end
|
23
|
+
|
24
|
+
def saved_data
|
25
|
+
data = {
|
26
|
+
'email' => self.email
|
27
|
+
}
|
28
|
+
data['name'] = self.name if self.name
|
29
|
+
data['id'] = self.id if self.id
|
30
|
+
data['auth_token'] = self.auth_token if self.auth_token
|
31
|
+
data
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.current_user_filename
|
35
|
+
"#{ Supso.user_supso_config_root }/current_user.json"
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.readme_filename
|
39
|
+
"#{ Supso.user_supso_config_root }/README.txt"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.current_user_from_file
|
43
|
+
if !File.exist?(User.current_user_filename)
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
|
47
|
+
user_data = {}
|
48
|
+
begin
|
49
|
+
user_data = JSON.parse(File.read(User.current_user_filename))
|
50
|
+
user_data = {} if !user_data.is_a?(Object)
|
51
|
+
rescue JSON::ParserError => err
|
52
|
+
user_data = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
if user_data['email'] || user_data['auth_token']
|
56
|
+
User.new(user_data['email'], user_data['name'], user_data['id'], user_data['auth_token'])
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.current_user
|
63
|
+
@@current_user ||= User.current_user_from_file
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.set_current_user!(email, name, id, auth_token = nil)
|
67
|
+
@@current_user = User.new(email, name, id, auth_token)
|
68
|
+
@@current_user.save_to_file!
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.attach_to_email!(email)
|
72
|
+
data = {
|
73
|
+
email: email,
|
74
|
+
}
|
75
|
+
|
76
|
+
response = Util.http_post("#{ Supso.supso_api_root }users/attach", data)
|
77
|
+
|
78
|
+
if response['success']
|
79
|
+
User.set_current_user!(response['user']['email'], response['user']['name'],
|
80
|
+
response['user']['id'])
|
81
|
+
else
|
82
|
+
puts response['reason']
|
83
|
+
# Anything here needed?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.log_in_with_password!(email, password)
|
88
|
+
data = {
|
89
|
+
email: email,
|
90
|
+
password: password,
|
91
|
+
}
|
92
|
+
|
93
|
+
response = Util.http_post("#{ Supso.supso_api_root }sign_in", data)
|
94
|
+
|
95
|
+
if response['success']
|
96
|
+
User.set_current_user!(response['user']['email'], response['user']['name'],
|
97
|
+
response['user']['id'], response['auth_token'])
|
98
|
+
if Organization.current_organization.nil?
|
99
|
+
Organization.set_current_organization!(response['organization']['name'], response['organization']['id'])
|
100
|
+
end
|
101
|
+
[true, nil]
|
102
|
+
else
|
103
|
+
[false, response['reason']]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.log_in_with_confirmation_token!(email, confirmation_token)
|
108
|
+
data = {
|
109
|
+
email: email,
|
110
|
+
confirmation_token: confirmation_token,
|
111
|
+
}
|
112
|
+
|
113
|
+
response = Util.http_post("#{ Supso.supso_api_root }users/confirm", data)
|
114
|
+
|
115
|
+
if response['success']
|
116
|
+
User.set_current_user!(response['user']['email'], response['user']['name'],
|
117
|
+
response['user']['id'], response['auth_token'])
|
118
|
+
if Organization.current_organization.nil?
|
119
|
+
Organization.set_current_organization!(response['organization']['name'], response['organization']['id'])
|
120
|
+
end
|
121
|
+
[true, nil]
|
122
|
+
else
|
123
|
+
[false, response['reason']]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.log_out!
|
128
|
+
if File.exists?(User.current_user_filename)
|
129
|
+
user = User.current_user
|
130
|
+
if user && user.auth_token
|
131
|
+
data = {
|
132
|
+
version: Supso::VERSION,
|
133
|
+
auth_token: user.auth_token,
|
134
|
+
user_id: user.id,
|
135
|
+
}
|
136
|
+
|
137
|
+
response = Util.http_post("#{ Supso.supso_api_root }sign_out", data)
|
138
|
+
|
139
|
+
if response['success']
|
140
|
+
# No need to say anything like 'logout succeeded'
|
141
|
+
else
|
142
|
+
puts response['reason']
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
File.delete(User.current_user_filename)
|
147
|
+
@@current_user = nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.save_user_supso_readme!
|
152
|
+
if !File.exists?(User.readme_filename)
|
153
|
+
readme_contents = File.open("#{ Supso.gem_root }/lib/templates/user_dir_readme.txt", 'r').read
|
154
|
+
Util.ensure_path_exists!(User.readme_filename)
|
155
|
+
file = File.open(User.readme_filename, 'w')
|
156
|
+
file << readme_contents
|
157
|
+
file.close
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/supso/util.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'bundler'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Supso
|
6
|
+
module Util
|
7
|
+
def Util.deep_merge(first, second)
|
8
|
+
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
9
|
+
first.merge(second, &merger)
|
10
|
+
end
|
11
|
+
|
12
|
+
def Util.detect_project_root
|
13
|
+
project_root = Dir.getwd
|
14
|
+
while true
|
15
|
+
if project_root == ""
|
16
|
+
project_root = nil
|
17
|
+
break
|
18
|
+
end
|
19
|
+
|
20
|
+
if File.exist?(project_root + '/Gemfile') ||
|
21
|
+
File.exist?(project_root + '/package.json') ||
|
22
|
+
File.exist?(project_root + '.supso')
|
23
|
+
break
|
24
|
+
end
|
25
|
+
|
26
|
+
detect_project_root_splits = project_root.split("/")
|
27
|
+
detect_project_root_splits = detect_project_root_splits[0..detect_project_root_splits.length - 2]
|
28
|
+
project_root = detect_project_root_splits.join("/")
|
29
|
+
end
|
30
|
+
|
31
|
+
project_root
|
32
|
+
end
|
33
|
+
|
34
|
+
def Util.ensure_path_exists!(full_path)
|
35
|
+
split_paths = full_path.split('/')
|
36
|
+
just_file_path = split_paths.pop
|
37
|
+
directory_path = split_paths.join('/')
|
38
|
+
FileUtils.mkdir_p(directory_path)
|
39
|
+
FileUtils.touch("#{ directory_path }/#{ just_file_path }")
|
40
|
+
end
|
41
|
+
|
42
|
+
def Util.has_command?(command)
|
43
|
+
!!Util.which(command)
|
44
|
+
end
|
45
|
+
|
46
|
+
def Util.which(command)
|
47
|
+
command = Util.sanitize_command(command)
|
48
|
+
response = `which #{ command }`
|
49
|
+
response && response.length > 0 ? response : nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def Util.sanitize_command(command)
|
53
|
+
command.gsub(/[^-_\w]/, '')
|
54
|
+
end
|
55
|
+
|
56
|
+
def Util.http_get(url)
|
57
|
+
json_headers = {
|
58
|
+
"Content-Type" => "application/json",
|
59
|
+
"Accept" => "application/json",
|
60
|
+
}
|
61
|
+
uri = URI.parse(url)
|
62
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
63
|
+
if url.start_with?('https://')
|
64
|
+
http.use_ssl = true
|
65
|
+
end
|
66
|
+
response = http.get(uri.path, json_headers)
|
67
|
+
|
68
|
+
if response.code.to_i == 200
|
69
|
+
return JSON.parse(response.body)
|
70
|
+
else
|
71
|
+
raise StandardError.new("Error #{ response } for #{ url }")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def Util.http_post(url, data = {})
|
76
|
+
json_headers = {
|
77
|
+
"Content-Type" => "application/json",
|
78
|
+
"Accept" => "application/json",
|
79
|
+
}
|
80
|
+
|
81
|
+
data[:version] = Supso::VERSION
|
82
|
+
|
83
|
+
uri = URI.parse(url)
|
84
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
85
|
+
if url.start_with?('https://')
|
86
|
+
http.use_ssl = true
|
87
|
+
end
|
88
|
+
response = http.post(uri.path, data.to_json, json_headers)
|
89
|
+
|
90
|
+
if response.code.to_i == 200
|
91
|
+
return JSON.parse(response.body)
|
92
|
+
else
|
93
|
+
raise StandardError.new("Error #{ response } for #{ url }")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def Util.is_email?(email)
|
98
|
+
!!/\A[^@]+@([^@\.]+\.)+[^@\.]+\z/.match(email)
|
99
|
+
end
|
100
|
+
|
101
|
+
def Util.pluralize(count, word)
|
102
|
+
if count == 1
|
103
|
+
word
|
104
|
+
else
|
105
|
+
"#{ word }s"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def Util.require_all_gems!
|
110
|
+
begin
|
111
|
+
Bundler.require(:default, :development, :test, :production)
|
112
|
+
rescue Gem::LoadError, Bundler::GemfileNotFound
|
113
|
+
# Keep going
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def Util.underscore_to_camelcase(str)
|
118
|
+
str.split('_').map{ |chunk| chunk.capitalize }.join
|
119
|
+
end
|
120
|
+
|
121
|
+
def Util.camelcase_to_underscore(str)
|
122
|
+
str.gsub(/::/, '/')
|
123
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
124
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
125
|
+
.tr("-", "_")
|
126
|
+
.downcase
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|