tmc-client 0.0.1
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/.gitignore +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +44 -0
- data/Readme.md +0 -0
- data/bin/tmc-client +42 -0
- data/lib/tmc-client/client.rb +386 -0
- data/lib/tmc-client/config.default.yml +2 -0
- data/lib/tmc-client/config.yml +0 -0
- data/lib/tmc-client/my_config.rb +56 -0
- data/lib/tmc-client/old-client.rb +208 -0
- data/lib/tmc-client.rb +30 -0
- data/specs/download_spec.rb +28 -0
- data/specs/ex.zip +0 -0
- data/specs/list_spec.rb +27 -0
- data/specs/submit_spec.rb +25 -0
- data/specs/update_ex.zip +0 -0
- data/specs/update_spec.rb +43 -0
- data/specs/update_universal_ex.zip +0 -0
- data/tmc +3 -0
- data/tmc-client.gemspec +24 -0
- metadata +212 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
coderay (1.0.9)
|
|
5
|
+
diff-lcs (1.1.3)
|
|
6
|
+
faraday (0.8.7)
|
|
7
|
+
multipart-post (~> 1.1)
|
|
8
|
+
fileutils (0.7)
|
|
9
|
+
rmagick (>= 2.13.1)
|
|
10
|
+
highline (1.6.18)
|
|
11
|
+
json (1.7.7)
|
|
12
|
+
metaclass (0.0.1)
|
|
13
|
+
method_source (0.8.1)
|
|
14
|
+
mocha (0.13.3)
|
|
15
|
+
metaclass (~> 0.0.1)
|
|
16
|
+
multipart-post (1.2.0)
|
|
17
|
+
pry (0.9.12)
|
|
18
|
+
coderay (~> 1.0.5)
|
|
19
|
+
method_source (~> 0.8)
|
|
20
|
+
slop (~> 3.4)
|
|
21
|
+
rake (10.0.4)
|
|
22
|
+
rmagick (2.13.2)
|
|
23
|
+
rspec (2.12.0)
|
|
24
|
+
rspec-core (~> 2.12.0)
|
|
25
|
+
rspec-expectations (~> 2.12.0)
|
|
26
|
+
rspec-mocks (~> 2.12.0)
|
|
27
|
+
rspec-core (2.12.2)
|
|
28
|
+
rspec-expectations (2.12.1)
|
|
29
|
+
diff-lcs (~> 1.1.3)
|
|
30
|
+
rspec-mocks (2.12.2)
|
|
31
|
+
slop (3.4.4)
|
|
32
|
+
|
|
33
|
+
PLATFORMS
|
|
34
|
+
ruby
|
|
35
|
+
|
|
36
|
+
DEPENDENCIES
|
|
37
|
+
faraday
|
|
38
|
+
fileutils
|
|
39
|
+
highline
|
|
40
|
+
json
|
|
41
|
+
mocha
|
|
42
|
+
pry
|
|
43
|
+
rake
|
|
44
|
+
rspec
|
data/Readme.md
ADDED
|
File without changes
|
data/bin/tmc-client
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'tmc-client/client'
|
|
5
|
+
|
|
6
|
+
@c = Client.new
|
|
7
|
+
|
|
8
|
+
commands = {
|
|
9
|
+
list: :list,
|
|
10
|
+
download: :download,
|
|
11
|
+
submit: :submit_exercise,
|
|
12
|
+
update: :update_exercise,
|
|
13
|
+
auth: :auth,
|
|
14
|
+
check: :check,
|
|
15
|
+
init: :init_course,
|
|
16
|
+
status: :status,
|
|
17
|
+
solution: :solution,
|
|
18
|
+
get: :get,
|
|
19
|
+
set: :set
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
command = ARGV[0].to_s
|
|
23
|
+
sub_arguments = ARGV.drop 1
|
|
24
|
+
|
|
25
|
+
unless commands[command.to_sym].nil?
|
|
26
|
+
@c.send(commands[command.to_sym], *sub_arguments) #unless command
|
|
27
|
+
else
|
|
28
|
+
puts "Unknown command"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
rescue LoadError => e
|
|
33
|
+
$stderr.puts <<-EOS
|
|
34
|
+
#{'*'*50}
|
|
35
|
+
puts "An error occurred. Make sure the command was triggered in the right directory. Also make sure you have a valid server address and authentication information. If these do not help, please contact an administrator."
|
|
36
|
+
#{'*'*50}
|
|
37
|
+
EOS
|
|
38
|
+
|
|
39
|
+
raise e
|
|
40
|
+
|
|
41
|
+
exit(1)
|
|
42
|
+
end
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'highline/import'
|
|
3
|
+
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'faraday'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require 'pry'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
require 'tempfile'
|
|
10
|
+
require 'pp'
|
|
11
|
+
require_relative 'my_config'
|
|
12
|
+
|
|
13
|
+
class Client
|
|
14
|
+
attr_accessor :courses, :config, :conn, :output, :input
|
|
15
|
+
def initialize(output=$stdout, input=$stdin)
|
|
16
|
+
@config = MyConfig.new
|
|
17
|
+
@output = output
|
|
18
|
+
@input = input
|
|
19
|
+
begin
|
|
20
|
+
@config.server_url ||= request_server_url
|
|
21
|
+
rescue
|
|
22
|
+
request_server_url
|
|
23
|
+
end
|
|
24
|
+
init_connection()
|
|
25
|
+
if @config.auth
|
|
26
|
+
begin
|
|
27
|
+
@courses = JSON.parse get_courses_json
|
|
28
|
+
rescue => e
|
|
29
|
+
auth
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
output.puts "No username/password. run tmc auth"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# stupid name - this should create connection, but not fetch courses.json!
|
|
37
|
+
def init_connection()
|
|
38
|
+
@conn = Faraday.new(:url => @config.server_url) do |faraday|
|
|
39
|
+
faraday.request :multipart
|
|
40
|
+
faraday.request :url_encoded # form-encode POST params
|
|
41
|
+
#faraday.response :logger # log requests to STDOUT We dont want to do this in production!
|
|
42
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if @config.auth
|
|
46
|
+
@conn.headers[Faraday::Request::Authorization::KEY] = @config.auth
|
|
47
|
+
else
|
|
48
|
+
auth
|
|
49
|
+
@conn.headers[Faraday::Request::Authorization::KEY] = @config.auth
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def get(*args)
|
|
55
|
+
target = args.first
|
|
56
|
+
case target
|
|
57
|
+
when 'url' then output.puts @config.server_url
|
|
58
|
+
else output.puts "No property selected"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def set(*args)
|
|
63
|
+
target = args.first
|
|
64
|
+
case target
|
|
65
|
+
when "url"
|
|
66
|
+
@config.server_url = args[1] if args.count >= 2
|
|
67
|
+
else output.puts "No property selected"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def check
|
|
72
|
+
output.puts get_courses_json
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_courses_json
|
|
76
|
+
data = @conn.get('courses.json', {api_version: 5}).body
|
|
77
|
+
raise "Error with autentikation" if data['error']
|
|
78
|
+
data
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Path can be relative or absolute
|
|
82
|
+
def is_universal_project?(path)
|
|
83
|
+
File.exists? File.join(path, ".universal")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def get_password(prompt="Enter Password")
|
|
87
|
+
ask(prompt) {|q| q.echo = false}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def auth
|
|
91
|
+
output.print "Username: "
|
|
92
|
+
username = @input.gets.chomp.strip
|
|
93
|
+
password = get_password("Password (typing is hidden): ")
|
|
94
|
+
@config.auth = nil
|
|
95
|
+
@conn.basic_auth(username, password)
|
|
96
|
+
@config.auth = @conn.headers[Faraday::Request::Authorization::KEY]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def request_server_url
|
|
100
|
+
output.print "Server url: "
|
|
101
|
+
@config.server_url = @input.gets.chomp.strip
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def get_real_name(headers)
|
|
105
|
+
name = headers['content-disposition']
|
|
106
|
+
name.split("\"").compact[-1]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def fetch_zip(zip_url)
|
|
110
|
+
@conn.get(zip_url)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def get_course_name
|
|
114
|
+
get_my_course['name']
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def list(mode=:courses)
|
|
118
|
+
mode = mode.to_sym
|
|
119
|
+
case mode
|
|
120
|
+
when :courses
|
|
121
|
+
list_courses
|
|
122
|
+
when :exercises
|
|
123
|
+
list_exercises
|
|
124
|
+
else
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def list_exercises(course_dir_name=nil)
|
|
129
|
+
course_dir_name = current_directory_name if course_dir_name.nil?
|
|
130
|
+
course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
|
|
131
|
+
raise "Invalid course name" if course.nil?
|
|
132
|
+
course["exercises"].each do |ex|
|
|
133
|
+
output.puts "#{ex['name']} #{ex['deadline']}" if ex["returnable"]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def list_courses
|
|
138
|
+
output.puts "No courses available. Make sure your server url is correct and that you have authenticated." if @courses.nil? or @courses["courses"].nil?
|
|
139
|
+
@courses['courses'].each do |course|
|
|
140
|
+
output.puts "#{course['name']}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def current_directory_name
|
|
145
|
+
File.basename(Dir.getwd)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def previous_directory_name
|
|
149
|
+
path = Dir.getwd
|
|
150
|
+
directories = path.split("/")
|
|
151
|
+
directories[directories.count - 2]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def init_course(course_name)
|
|
155
|
+
FileUtils.mkdir_p(course_name)
|
|
156
|
+
output.print "Would you like to download all available exercises? Yn"
|
|
157
|
+
if ["", "y", "Y"].include? @input.gets.strip.chomp
|
|
158
|
+
Dir.chdir(course_name) do
|
|
159
|
+
course = @courses['courses'].select { |course| course['name'] == course_name }.first
|
|
160
|
+
download_new_exercises
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def download(*args)
|
|
166
|
+
if args.include? "all" or args.include? "-a" or args.include? "--all"
|
|
167
|
+
download_new_exercises
|
|
168
|
+
else
|
|
169
|
+
download_new_exercise(*args)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def solution(exercise_dir_name=nil)
|
|
174
|
+
# Initialize course and exercise names to identify exercise to submit (from json)
|
|
175
|
+
if exercise_dir_name.nil?
|
|
176
|
+
exercise_dir_name = current_directory_name
|
|
177
|
+
course_dir_name = previous_directory_name
|
|
178
|
+
else
|
|
179
|
+
course_dir_name = current_directory_name
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
exercise_dir_name.chomp("/")
|
|
183
|
+
# Find course and exercise ids
|
|
184
|
+
course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
|
|
185
|
+
raise "Invalid course name" if course.nil?
|
|
186
|
+
exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
|
|
187
|
+
raise "Invalid exercise name" if exercise.nil?
|
|
188
|
+
|
|
189
|
+
update_from_zip(exercise['solution_zip_url'], exercise_dir_name, course_dir_name, exercise, course)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def update_from_zip(zip_url, exercise_dir_name, course_dir_name, exercise, course)
|
|
193
|
+
zip = fetch_zip(zip_url)
|
|
194
|
+
output.puts "URL: #{zip_url}"
|
|
195
|
+
work_dir = Dir.pwd
|
|
196
|
+
to_dir = if Dir.pwd.chomp("/").split("/").last == exercise_dir_name
|
|
197
|
+
work_dir
|
|
198
|
+
else
|
|
199
|
+
File.join(work_dir, exercise_dir_name)
|
|
200
|
+
end
|
|
201
|
+
Dir.mktmpdir do |tmpdir|
|
|
202
|
+
Dir.chdir tmpdir do
|
|
203
|
+
File.open("tmp.zip", 'wb') {|file| file.write(zip.body)}
|
|
204
|
+
`unzip -n tmp.zip && rm tmp.zip`
|
|
205
|
+
files = Dir.glob('**/*')
|
|
206
|
+
all_selected = false
|
|
207
|
+
files.each do |file|
|
|
208
|
+
next if file == exercise_dir_name or File.directory? file
|
|
209
|
+
output.puts "Want to update #{file}? Yn[A]" unless all_selected
|
|
210
|
+
input = @input.gets.chomp.strip unless all_selected
|
|
211
|
+
all_selected = true if input == "A" or input == "a"
|
|
212
|
+
if all_selected or (["", "y", "Y"].include? input)
|
|
213
|
+
begin
|
|
214
|
+
to = File.join(to_dir,file.split("/")[1..-1].join("/"))
|
|
215
|
+
output.puts "copying #{file} to #{to}"
|
|
216
|
+
unless File.directory? to
|
|
217
|
+
FileUtils.mkdir_p(to.split("/")[0..-2].join("/"))
|
|
218
|
+
else
|
|
219
|
+
FileUtils.mkdir_p(to)
|
|
220
|
+
end
|
|
221
|
+
FileUtils.cp_r(file, to)
|
|
222
|
+
rescue ArgumentError => e
|
|
223
|
+
output.puts "An error occurred #{e}"
|
|
224
|
+
end
|
|
225
|
+
elsif input == "b"
|
|
226
|
+
binding.pry
|
|
227
|
+
else
|
|
228
|
+
output.puts "Skipping file #{file}"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def update_automatically_detected_project_from_zip(zip_url, exercise_dir_name, course_dir_name, exercise, course)
|
|
236
|
+
zip = fetch_zip(exercise['zip_url'])
|
|
237
|
+
work_dir = Dir.pwd
|
|
238
|
+
to_dir = if Dir.pwd.chomp("/").split("/").last == exercise_dir_name
|
|
239
|
+
work_dir
|
|
240
|
+
else
|
|
241
|
+
File.join(work_dir, exercise_dir_name)
|
|
242
|
+
end
|
|
243
|
+
Dir.mktmpdir do |tmpdir|
|
|
244
|
+
Dir.chdir tmpdir do
|
|
245
|
+
File.open("tmp.zip", 'wb') {|file| file.write(zip.body)}
|
|
246
|
+
`unzip -n tmp.zip && rm tmp.zip`
|
|
247
|
+
files = Dir.glob('**/*')
|
|
248
|
+
|
|
249
|
+
files.each do |file|
|
|
250
|
+
next if file == exercise_dir_name or file.to_s.include? "src" or File.directory? file
|
|
251
|
+
begin
|
|
252
|
+
to = File.join(to_dir,file.split("/")[1..-1].join("/"))
|
|
253
|
+
output.puts "copying #{file} to #{to}"
|
|
254
|
+
unless File.directory? to
|
|
255
|
+
FileUtils.mkdir_p(to.split("/")[0..-2].join("/"))
|
|
256
|
+
else
|
|
257
|
+
FileUtils.mkdir_p(to)
|
|
258
|
+
end
|
|
259
|
+
FileUtils.cp_r(file, to)
|
|
260
|
+
rescue ArgumentError => e
|
|
261
|
+
output.puts "An error occurred #{e}"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def filter_returnable(exercises)
|
|
269
|
+
exercises.collect { |ex| ex['name'] if ex['returnable'] }.compact
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def download_new_exercises(course_dir_name=nil)
|
|
273
|
+
course_dir_name = current_directory_name if course_dir_name.nil?
|
|
274
|
+
course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
|
|
275
|
+
raise "Invalid course name" if course.nil?
|
|
276
|
+
filter_returnable(course['exercises']).each do |ex_name|
|
|
277
|
+
begin
|
|
278
|
+
download_new_exercise(ex_name)
|
|
279
|
+
rescue
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def download_new_exercise(exercise_dir_name=nil)
|
|
285
|
+
# Initialize course and exercise names to identify exercise to submit (from json)
|
|
286
|
+
course_dir_name = current_directory_name
|
|
287
|
+
exercise_dir_name.chomp("/")
|
|
288
|
+
# Find course and exercise ids
|
|
289
|
+
course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
|
|
290
|
+
raise "Invalid course name" if course.nil?
|
|
291
|
+
exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
|
|
292
|
+
raise "Invalid exercise name" if exercise.nil?
|
|
293
|
+
|
|
294
|
+
raise "Exercise already downloaded" if File.exists? exercise['name']
|
|
295
|
+
zip = fetch_zip(exercise['zip_url'])
|
|
296
|
+
File.open("tmp.zip", 'wb') {|file| file.write(zip.body)}
|
|
297
|
+
`unzip -n tmp.zip && rm tmp.zip`
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Filepath can be either relative or absolute
|
|
301
|
+
def zip_file_content(filepath)
|
|
302
|
+
`zip -r -q tmp_submit.zip #{filepath}`
|
|
303
|
+
#`zip -r -q - #{filepath}`
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Call in exercise root
|
|
307
|
+
# Zipping to stdout zip -r -q - tmc
|
|
308
|
+
def submit_exercise(*args)
|
|
309
|
+
# Initialize course and exercise names to identify exercise to submit (from json)
|
|
310
|
+
if args.count == 0 or args.all? { |arg| arg.start_with? "-" }
|
|
311
|
+
exercise_dir_name = current_directory_name
|
|
312
|
+
course_dir_name = previous_directory_name
|
|
313
|
+
zip_file_content(".")
|
|
314
|
+
else
|
|
315
|
+
exercise_dir_name = args.first
|
|
316
|
+
course_dir_name = current_directory_name
|
|
317
|
+
zip_file_content(exercise_dir_name)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
exercise_dir_name.chomp("/")
|
|
321
|
+
exercise_id = 0
|
|
322
|
+
# Find course and exercise ids
|
|
323
|
+
course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
|
|
324
|
+
raise "Invalid course name" if course.nil?
|
|
325
|
+
exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
|
|
326
|
+
raise "Invalid exercise name" if exercise.nil?
|
|
327
|
+
|
|
328
|
+
# Submit
|
|
329
|
+
payload={:submission => {}}
|
|
330
|
+
payload[:request_review] = true if args.include? "--request-review" or args.include? "-r" or args.include? "--review"
|
|
331
|
+
payload[:paste] = true if args.include? "--paste" or args.include? "-p" or args.include? "--public"
|
|
332
|
+
payload[:submission][:file] = Faraday::UploadIO.new('tmp_submit.zip', 'application/zip')
|
|
333
|
+
response = @conn.post "/exercises/#{exercise['id']}/submissions.json?api_version=5&client=netbeans_plugin&client_version=1", payload
|
|
334
|
+
submission_url = JSON.parse(response.body)['submission_url']
|
|
335
|
+
puts "Submission url: #{submission_url}"
|
|
336
|
+
|
|
337
|
+
if (args & %w{-q --quiet -s --silent}).empty?
|
|
338
|
+
while status(submission_url) == "processing"
|
|
339
|
+
sleep(1)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
FileUtils.rm 'tmp_submit.zip'
|
|
344
|
+
payload
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def status(submission_id_or_url)
|
|
348
|
+
url = (submission_id_or_url.include? "submissions") ? submission_id_or_url : "/submissions/#{submission_id_or_url}.json?api_version=5"
|
|
349
|
+
json = JSON.parse(@conn.get(url).body)
|
|
350
|
+
if json['status'] != 'processing'
|
|
351
|
+
puts "Status: #{json['status']}"
|
|
352
|
+
puts "Points: #{json['points'].inspect}"
|
|
353
|
+
puts "Tests:"
|
|
354
|
+
json['test_cases'].each do |test|
|
|
355
|
+
puts "#{test['name']} : #{(test['successful']) ? 'Ok' : 'Fail'}#{(test['message'].nil?) ? '' : (' : ' + test['message'])}"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
json['status']
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def update_exercise(exercise_dir_name=nil)
|
|
362
|
+
# Initialize course and exercise names to identify exercise to submit (from json)
|
|
363
|
+
is_universal = false
|
|
364
|
+
if exercise_dir_name.nil?
|
|
365
|
+
exercise_dir_name = current_directory_name
|
|
366
|
+
course_dir_name = previous_directory_name
|
|
367
|
+
is_universal = true if File.exists? ".universal"
|
|
368
|
+
else
|
|
369
|
+
course_dir_name = current_directory_name
|
|
370
|
+
is_universal = true if File.exists?(File.join("#{exercise_dir_name}", ".universal"))
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
exercise_dir_name.chomp("/")
|
|
374
|
+
# Find course and exercise ids
|
|
375
|
+
course = @courses['courses'].select { |course| course['name'] == course_dir_name }.first
|
|
376
|
+
raise "Invalid course name" if course.nil?
|
|
377
|
+
exercise = course["exercises"].select { |ex| ex["name"] == exercise_dir_name }.first
|
|
378
|
+
raise "Invalid exercise name" if exercise.nil?
|
|
379
|
+
|
|
380
|
+
if is_universal
|
|
381
|
+
update_from_zip(exercise['zip_url'], exercise_dir_name, course_dir_name, exercise, course)
|
|
382
|
+
else
|
|
383
|
+
update_automatically_detected_project_from_zip(exercise['zip_url'], exercise_dir_name, course_dir_name, exercise, course)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class MyConfig
|
|
2
|
+
attr_accessor :config
|
|
3
|
+
|
|
4
|
+
def initialize
|
|
5
|
+
@config = get_config
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get_config
|
|
9
|
+
begin
|
|
10
|
+
yaml = YAML::load(File.open(File.join(File.dirname(File.expand_path(__FILE__)), "config.yml")))
|
|
11
|
+
rescue
|
|
12
|
+
raise "Error loading config.yml"
|
|
13
|
+
end
|
|
14
|
+
if yaml then yaml else {} end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def save_config
|
|
18
|
+
File.open(File.open(File.join(File.dirname(File.expand_path(__FILE__)), "config.yml")), "w") {|f| f.write(config.to_yaml) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def server_url
|
|
22
|
+
@config[:server_url] unless @config.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def server_url=(url)
|
|
26
|
+
@config[:server_url] = url
|
|
27
|
+
save_config
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def course_id
|
|
31
|
+
@config[:course_id]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def course_id=(id)
|
|
35
|
+
@config[:course_id] = id
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def save_course_path
|
|
39
|
+
@config[:save_course_path]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def save_course_path=(path)
|
|
43
|
+
path= "#{path}/" unless path[-1] == "/"
|
|
44
|
+
@config[:save_course_path]=path
|
|
45
|
+
save_config
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def auth=(auth)
|
|
49
|
+
@config[:auth] = auth
|
|
50
|
+
save_config
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def auth
|
|
54
|
+
@config[:auth] unless @config.nil?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require 'pry'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'tempfile'
|
|
8
|
+
require 'pp'
|
|
9
|
+
require_relative 'my_config'
|
|
10
|
+
|
|
11
|
+
class Client
|
|
12
|
+
attr_accessor :courses, :config, :conn
|
|
13
|
+
def initialize()
|
|
14
|
+
@config = MyConfig.new
|
|
15
|
+
response = get_connection(@config.username,@config.password)
|
|
16
|
+
@courses = JSON.parse response.body
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# stupid name - this should create connection, but not fetch courses.json!
|
|
20
|
+
def get_connection(username, password)
|
|
21
|
+
@conn = Faraday.new(:url => @config.server_url) do |faraday|
|
|
22
|
+
faraday.request :url_encoded # form-encode POST params
|
|
23
|
+
faraday.response :logger # log requests to STDOUT We dont want to do this in production!
|
|
24
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
|
25
|
+
end
|
|
26
|
+
@conn.basic_auth(username, password) # )
|
|
27
|
+
@conn.get 'courses.json', {api_version: 5}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_real_name(headers)
|
|
31
|
+
name = headers['content-disposition']
|
|
32
|
+
name.split("\"").compact[-1]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def download_file(path)
|
|
36
|
+
ask_course_path unless @config.save_course_path
|
|
37
|
+
loaded_file = @conn.get(path)
|
|
38
|
+
real_name = get_real_name(loaded_file.headers)
|
|
39
|
+
fname= path.split("/")[-1]
|
|
40
|
+
course_name = get_course_name
|
|
41
|
+
path = "#{config.save_course_path}#{course_name}/"
|
|
42
|
+
begin
|
|
43
|
+
FileUtils.mkdir_p(path) unless File.exists? path
|
|
44
|
+
rescue Errno::EISDIR
|
|
45
|
+
binding.pry
|
|
46
|
+
end
|
|
47
|
+
file_path = "#{path}#{real_name}"
|
|
48
|
+
begin
|
|
49
|
+
File.open(file_path, 'wb') {|file| file.write(loaded_file.body)} #unless File.exists? file_path
|
|
50
|
+
rescue Errno::EISDIR
|
|
51
|
+
binding.pry
|
|
52
|
+
end
|
|
53
|
+
result = `unzip -o #{file_path} -d #{path}`
|
|
54
|
+
FileUtils.rm file_path
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def fetch_zip(zip_url)
|
|
58
|
+
@conn.get(zip_url)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ask_course_path
|
|
62
|
+
puts "where to download?"
|
|
63
|
+
@config.save_course_path=gets.chomp
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def get_my_course(course_name=config.course_id)
|
|
67
|
+
list = @courses['courses'].select {|course| course['id'] == config.course_id}
|
|
68
|
+
list[0]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def list(mode=nil)
|
|
73
|
+
if mode.nil?
|
|
74
|
+
puts "What would you like to list? Perhaps exercises with list exercises, or active projects with list active?"
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
send("list_#{mode}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def list_exercises
|
|
82
|
+
list = get_my_course['exercises'].map {|ex| ex['name']}
|
|
83
|
+
print_exercises(list)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def print_exercises(hash)
|
|
87
|
+
list = hash.each {|ex| puts ex['name']}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def list_active
|
|
91
|
+
list = get_my_course['exercises'].select {|ex| ex['returnable'] == true and ex['completed']==false}
|
|
92
|
+
print_exercises(list)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ask_for_course_id
|
|
96
|
+
list_courses
|
|
97
|
+
@config.course_id= gets.chomp
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def download_all_available
|
|
101
|
+
list = get_my_course['exercises']
|
|
102
|
+
ask_course_id if list.empty?
|
|
103
|
+
list.each do |ex|
|
|
104
|
+
download_file(ex['zip_url'])
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Filepath can be either relative or absolute
|
|
109
|
+
def zip_file_content(filepath)
|
|
110
|
+
`zip -r -q - #{filepath}`
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Call in exercise root
|
|
114
|
+
# Zipping to stdout zip -r -q - tmc
|
|
115
|
+
def update_exercise(exercise_dir_name=nil)
|
|
116
|
+
# Initialize course and exercise names to identify exercise to submit (from json)
|
|
117
|
+
if exercise_dir_name.nil?
|
|
118
|
+
exercise_dir_name = get_current_directory_name
|
|
119
|
+
course_dir_name = get_previous_directory_name
|
|
120
|
+
else
|
|
121
|
+
course_dir_name = get_current_directory_name
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
exercise_dir_name.chomp("/")
|
|
125
|
+
exercise_id = 0
|
|
126
|
+
zip_url=""
|
|
127
|
+
# Find course and exercise ids
|
|
128
|
+
@courses["courses"].each do |course|
|
|
129
|
+
if course["name"] == course_dir_name
|
|
130
|
+
course["exercises"].each do |exercise|
|
|
131
|
+
if exercise["name"] == exercise_dir_name
|
|
132
|
+
exercise_id = exercise["id"]
|
|
133
|
+
zip_url = exercise["zip_url"]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
raise "UrlNotFound" if zip_url.empty?
|
|
139
|
+
# and now download the zip file and extract it into a tmpdir.
|
|
140
|
+
# Then copy all files except in src to this dir
|
|
141
|
+
zip = fetch_zip(zip_url)
|
|
142
|
+
Dir.mktmpdir do |dir|
|
|
143
|
+
begin
|
|
144
|
+
File.open("#{dir}/tmp.zip", 'wb') {|file| file.write(zip.body)} #unless File.exists? file_path
|
|
145
|
+
rescue Errno::EISDIR
|
|
146
|
+
binding.pry
|
|
147
|
+
end
|
|
148
|
+
result = `unzip -o #{dir}/tmp.zip -d #{dir}/`
|
|
149
|
+
#now its in #{dir}/#{exercise_dir_name}
|
|
150
|
+
from_dir = "#{dir}/#{exercise_dir_name}/"
|
|
151
|
+
to_dir = "#{exercise_dir_name}/"
|
|
152
|
+
copy_updateable_files(from_dir, to_dir)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def copy_updateable_files(from_dir, to_dir)
|
|
157
|
+
to_dir = "." if get_current_directory_name == to_dir.chomp("/")
|
|
158
|
+
do_not_update = %w(src)
|
|
159
|
+
Dir.glob("#{from_dir}**") do |file|
|
|
160
|
+
filename = file.split("/")[-1]
|
|
161
|
+
next if do_not_update.include? filename
|
|
162
|
+
FileUtils.rm_rf("#{to_dir}/file")
|
|
163
|
+
FileUtils.cp_r(file,"#{to_dir}/")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Call in exercise root
|
|
168
|
+
>>>>>>> update-action
|
|
169
|
+
def submit_exercise(exercise_dir_name=nil)
|
|
170
|
+
# Initialize course and exercise names to identify exercise to submit (from json)
|
|
171
|
+
if exercise_dir_name.nil?
|
|
172
|
+
exercise_dir_name = current_directory_name
|
|
173
|
+
course_dir_name = previous_directory_name
|
|
174
|
+
zipped = zip_file_content(".")
|
|
175
|
+
else
|
|
176
|
+
#<<<<<<< HEAD
|
|
177
|
+
# course_dir_name = current_directory_name
|
|
178
|
+
# zipped = zip_file_content(exercise_dir_name)
|
|
179
|
+
#=======
|
|
180
|
+
course_dir_name = get_current_directory_name
|
|
181
|
+
# Zip folder
|
|
182
|
+
`zip -r zipped.zip #{exercise_dir_name}`
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
exercise_dir_name.chomp("/")
|
|
186
|
+
exercise_id = 0
|
|
187
|
+
# Find course and exercise ids
|
|
188
|
+
@courses["courses"].each do |course|
|
|
189
|
+
if course["name"] == course_dir_name
|
|
190
|
+
course["exercises"].each do |exercise|
|
|
191
|
+
if exercise["name"] == exercise_dir_name
|
|
192
|
+
exercise_id = exercise["id"]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Submit
|
|
199
|
+
payload = {:submission => zipped}
|
|
200
|
+
@conn.post do |req|
|
|
201
|
+
req.url "/exercises/#{exercise_id}/submissions"
|
|
202
|
+
req.headers["Content-Type"] = "application/zip"
|
|
203
|
+
req.body = "#{payload}"
|
|
204
|
+
end
|
|
205
|
+
payload
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
end
|
data/lib/tmc-client.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require_relative 'tmc-client/client'
|
|
2
|
+
|
|
3
|
+
@c = Client.new
|
|
4
|
+
|
|
5
|
+
commands = {
|
|
6
|
+
list: :list,
|
|
7
|
+
download: :download,
|
|
8
|
+
submit: :submit_exercise,
|
|
9
|
+
update: :update_exercise,
|
|
10
|
+
auth: :auth,
|
|
11
|
+
check: :check,
|
|
12
|
+
init: :init_course,
|
|
13
|
+
status: :status,
|
|
14
|
+
solution: :solution,
|
|
15
|
+
get: :get,
|
|
16
|
+
set: :set
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
command = ARGV[0].to_s
|
|
20
|
+
sub_arguments = ARGV.drop 1
|
|
21
|
+
|
|
22
|
+
begin
|
|
23
|
+
unless commands[command.to_sym].nil?
|
|
24
|
+
@c.send(commands[command.to_sym], *sub_arguments) #unless command
|
|
25
|
+
else
|
|
26
|
+
puts "Unknown command"
|
|
27
|
+
end
|
|
28
|
+
rescue
|
|
29
|
+
puts "An error occurred. Make sure the command was triggered in the right directory. Also make sure you have a valid server address and authentication information. If these do not help, please contact an administrator."
|
|
30
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require_relative '../lib/tmc-client/client.rb'
|
|
2
|
+
require 'rspec'
|
|
3
|
+
require 'mocha/setup'
|
|
4
|
+
|
|
5
|
+
describe Client do
|
|
6
|
+
subject do
|
|
7
|
+
c = Client.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "should be able to download a zip" do
|
|
11
|
+
received_zip_data = mock("faraday_object")
|
|
12
|
+
received_zip_data.expects(:body).returns(File.read(File.join(File.dirname(File.expand_path(__FILE__)), "ex.zip")))
|
|
13
|
+
subject.expects(:fetch_zip).returns(received_zip_data)
|
|
14
|
+
current_dir = subject.current_directory_name
|
|
15
|
+
subject.courses = { "courses" => [{ "name" => current_dir, "exercises" => [{ "name" => "ex", "returnable" => true }, {"name" => "old", "returnable" => false}] }] }
|
|
16
|
+
|
|
17
|
+
subject.download_new_exercise("ex")
|
|
18
|
+
file_content = File.read("ex.rb")
|
|
19
|
+
file_content.include?("def method").should == true
|
|
20
|
+
`rm ex.rb`
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should not download unreturnable exercises" do
|
|
24
|
+
filtered = subject.filter_returnable([{ "name" => "true", "returnable" => true}, {"name" => "false", "returnable" => false }])
|
|
25
|
+
filtered.count.should == 1
|
|
26
|
+
filtered.first.should == "true"
|
|
27
|
+
end
|
|
28
|
+
end
|
data/specs/ex.zip
ADDED
|
Binary file
|
data/specs/list_spec.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require_relative '../lib/tmc-client/client.rb'
|
|
2
|
+
require 'rspec'
|
|
3
|
+
require 'mocha'
|
|
4
|
+
|
|
5
|
+
describe Client do
|
|
6
|
+
subject do
|
|
7
|
+
Client.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "should print all course names when listing courses" do
|
|
11
|
+
subject.courses = {"courses" => [{ "name" => "test_course1"}, {"name" => "test_course2" }] }
|
|
12
|
+
output = mock("output")
|
|
13
|
+
output.expects(:puts).with("test_course1")
|
|
14
|
+
output.expects(:puts).with("test_course2")
|
|
15
|
+
subject.output = output
|
|
16
|
+
subject.list_courses
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should print all exercise names when listing exercises of a course" do
|
|
20
|
+
subject.courses = {"courses" => [{ "name" => "test_course1", "exercises" => [{"name" => "test_ex1"}, {"name" => "test_ex2"}]}] }
|
|
21
|
+
output = mock("output")
|
|
22
|
+
output.expects(:puts).with("test_ex1")
|
|
23
|
+
output.expects(:puts).with("test_ex2")
|
|
24
|
+
subject.output = output
|
|
25
|
+
subject.list_exercises("test_course1")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require_relative '../lib/tmc-client/client.rb'
|
|
2
|
+
require 'rspec'
|
|
3
|
+
require 'mocha'
|
|
4
|
+
|
|
5
|
+
describe Client do
|
|
6
|
+
subject do
|
|
7
|
+
Client.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
its(:current_directory_name) { should == `pwd`.split("/").last.chomp }
|
|
11
|
+
its(:previous_directory_name) { should == `pwd`.split("/")[-2].chomp }
|
|
12
|
+
|
|
13
|
+
it "should be able to produce some zip contents" do
|
|
14
|
+
file = Tempfile.new("tmp")
|
|
15
|
+
file.write("content")
|
|
16
|
+
file.close
|
|
17
|
+
subject.zip_file_content(file.path)
|
|
18
|
+
zip_content = File.read("tmp_submit.zip")
|
|
19
|
+
`rm tmp_submit.zip`
|
|
20
|
+
file.unlink
|
|
21
|
+
|
|
22
|
+
zip_content.should_not be_nil
|
|
23
|
+
zip_content.should_not == ""
|
|
24
|
+
end
|
|
25
|
+
end
|
data/specs/update_ex.zip
ADDED
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require_relative '../lib/tmc-client/client.rb'
|
|
2
|
+
require 'rspec'
|
|
3
|
+
require 'mocha/setup'
|
|
4
|
+
|
|
5
|
+
describe Client do
|
|
6
|
+
subject do
|
|
7
|
+
c = Client.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
after(:each) do
|
|
11
|
+
`rm -r update_universal_ex`
|
|
12
|
+
`rm -r update_ex`
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should download all but contents of src folder with normal projects" do
|
|
16
|
+
received_zip_data = mock("faraday_object")
|
|
17
|
+
received_zip_data.expects(:body).returns(File.read(File.join(File.dirname(File.expand_path(__FILE__)), "update_ex.zip")))
|
|
18
|
+
subject.expects(:fetch_zip).returns(received_zip_data)
|
|
19
|
+
current_dir = subject.current_directory_name
|
|
20
|
+
subject.courses = { "courses" => [{ "name" => current_dir, "exercises" => [{ "name" => "update_ex", "returnable" => true }, {"name" => "old", "returnable" => false}] }] }
|
|
21
|
+
|
|
22
|
+
subject.update_exercise("update_ex")
|
|
23
|
+
File.exists?("update_ex/included.txt").should == true
|
|
24
|
+
File.exists?("update_ex/src/not_included.txt").should == false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should download all files when universal project" do
|
|
28
|
+
FileUtils.mkdir_p(File.join("update_universal_ex", ".universal"))
|
|
29
|
+
|
|
30
|
+
received_zip_data = mock("faraday_object")
|
|
31
|
+
received_zip_data.expects(:body).returns(File.read(File.join(File.dirname(File.expand_path(__FILE__)), "update_universal_ex.zip")))
|
|
32
|
+
input = mock("input")
|
|
33
|
+
input.expects(:gets).returns("A")
|
|
34
|
+
subject.input = input
|
|
35
|
+
subject.expects(:fetch_zip).returns(received_zip_data)
|
|
36
|
+
current_dir = subject.current_directory_name
|
|
37
|
+
subject.courses = { "courses" => [{ "name" => current_dir, "exercises" => [{ "name" => "update_universal_ex", "returnable" => true }, {"name" => "old", "returnable" => false}] }] }
|
|
38
|
+
|
|
39
|
+
subject.update_exercise("update_universal_ex")
|
|
40
|
+
File.exists?("update_universal_ex/included.txt").should == true
|
|
41
|
+
File.exists?("update_universal_ex/src/included.txt").should == true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
Binary file
|
data/tmc-client.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Gem::Specification.new do |s|
|
|
2
|
+
s.name = 'tmc-client'
|
|
3
|
+
s.version = '0.0.1'
|
|
4
|
+
s.platform = Gem::Platform::RUBY
|
|
5
|
+
s.summary = 'TestMyCode Commandline client'
|
|
6
|
+
s.authors = ['Jarmo Isotalo', 'Tony Kovanen']
|
|
7
|
+
s.homepage = "https://github.com/TMCee/tmc-client/"
|
|
8
|
+
s.files = `git ls-files`.split("\n")
|
|
9
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
10
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
11
|
+
s.require_paths = ["lib"]
|
|
12
|
+
|
|
13
|
+
s.add_dependency "json"
|
|
14
|
+
s.add_dependency "faraday"
|
|
15
|
+
s.add_dependency "pry"
|
|
16
|
+
s.add_dependency "fileutils"
|
|
17
|
+
s.add_dependency "mocha"
|
|
18
|
+
s.add_dependency "highline"
|
|
19
|
+
s.add_dependency "rake"
|
|
20
|
+
s.add_dependency "rspec"
|
|
21
|
+
|
|
22
|
+
s.add_development_dependency "rspec", "~> 2.8"
|
|
23
|
+
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tmc-client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Jarmo Isotalo
|
|
9
|
+
- Tony Kovanen
|
|
10
|
+
autorequire:
|
|
11
|
+
bindir: bin
|
|
12
|
+
cert_chain: []
|
|
13
|
+
date: 2013-05-03 00:00:00.000000000 Z
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: json
|
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
|
18
|
+
none: false
|
|
19
|
+
requirements:
|
|
20
|
+
- - ! '>='
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
none: false
|
|
27
|
+
requirements:
|
|
28
|
+
- - ! '>='
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
version: '0'
|
|
31
|
+
- !ruby/object:Gem::Dependency
|
|
32
|
+
name: faraday
|
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
|
34
|
+
none: false
|
|
35
|
+
requirements:
|
|
36
|
+
- - ! '>='
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
none: false
|
|
43
|
+
requirements:
|
|
44
|
+
- - ! '>='
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: pry
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
none: false
|
|
51
|
+
requirements:
|
|
52
|
+
- - ! '>='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
type: :runtime
|
|
56
|
+
prerelease: false
|
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
58
|
+
none: false
|
|
59
|
+
requirements:
|
|
60
|
+
- - ! '>='
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
- !ruby/object:Gem::Dependency
|
|
64
|
+
name: fileutils
|
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
|
66
|
+
none: false
|
|
67
|
+
requirements:
|
|
68
|
+
- - ! '>='
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
type: :runtime
|
|
72
|
+
prerelease: false
|
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
74
|
+
none: false
|
|
75
|
+
requirements:
|
|
76
|
+
- - ! '>='
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
- !ruby/object:Gem::Dependency
|
|
80
|
+
name: mocha
|
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
|
82
|
+
none: false
|
|
83
|
+
requirements:
|
|
84
|
+
- - ! '>='
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
type: :runtime
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
none: false
|
|
91
|
+
requirements:
|
|
92
|
+
- - ! '>='
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
- !ruby/object:Gem::Dependency
|
|
96
|
+
name: highline
|
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
|
98
|
+
none: false
|
|
99
|
+
requirements:
|
|
100
|
+
- - ! '>='
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
none: false
|
|
107
|
+
requirements:
|
|
108
|
+
- - ! '>='
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rake
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
none: false
|
|
115
|
+
requirements:
|
|
116
|
+
- - ! '>='
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
type: :runtime
|
|
120
|
+
prerelease: false
|
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
122
|
+
none: false
|
|
123
|
+
requirements:
|
|
124
|
+
- - ! '>='
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '0'
|
|
127
|
+
- !ruby/object:Gem::Dependency
|
|
128
|
+
name: rspec
|
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
|
130
|
+
none: false
|
|
131
|
+
requirements:
|
|
132
|
+
- - ! '>='
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0'
|
|
135
|
+
type: :runtime
|
|
136
|
+
prerelease: false
|
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
138
|
+
none: false
|
|
139
|
+
requirements:
|
|
140
|
+
- - ! '>='
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '0'
|
|
143
|
+
- !ruby/object:Gem::Dependency
|
|
144
|
+
name: rspec
|
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
|
146
|
+
none: false
|
|
147
|
+
requirements:
|
|
148
|
+
- - ~>
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '2.8'
|
|
151
|
+
type: :development
|
|
152
|
+
prerelease: false
|
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
154
|
+
none: false
|
|
155
|
+
requirements:
|
|
156
|
+
- - ~>
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '2.8'
|
|
159
|
+
description:
|
|
160
|
+
email:
|
|
161
|
+
executables:
|
|
162
|
+
- tmc-client
|
|
163
|
+
extensions: []
|
|
164
|
+
extra_rdoc_files: []
|
|
165
|
+
files:
|
|
166
|
+
- .gitignore
|
|
167
|
+
- .travis.yml
|
|
168
|
+
- Gemfile
|
|
169
|
+
- Gemfile.lock
|
|
170
|
+
- Readme.md
|
|
171
|
+
- bin/tmc-client
|
|
172
|
+
- lib/tmc-client.rb
|
|
173
|
+
- lib/tmc-client/client.rb
|
|
174
|
+
- lib/tmc-client/config.default.yml
|
|
175
|
+
- lib/tmc-client/config.yml
|
|
176
|
+
- lib/tmc-client/my_config.rb
|
|
177
|
+
- lib/tmc-client/old-client.rb
|
|
178
|
+
- specs/download_spec.rb
|
|
179
|
+
- specs/ex.zip
|
|
180
|
+
- specs/list_spec.rb
|
|
181
|
+
- specs/submit_spec.rb
|
|
182
|
+
- specs/update_ex.zip
|
|
183
|
+
- specs/update_spec.rb
|
|
184
|
+
- specs/update_universal_ex.zip
|
|
185
|
+
- tmc
|
|
186
|
+
- tmc-client-0.0.1.gem
|
|
187
|
+
- tmc-client.gemspec
|
|
188
|
+
homepage: https://github.com/TMCee/tmc-client/
|
|
189
|
+
licenses: []
|
|
190
|
+
post_install_message:
|
|
191
|
+
rdoc_options: []
|
|
192
|
+
require_paths:
|
|
193
|
+
- lib
|
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
195
|
+
none: false
|
|
196
|
+
requirements:
|
|
197
|
+
- - ! '>='
|
|
198
|
+
- !ruby/object:Gem::Version
|
|
199
|
+
version: '0'
|
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
|
+
none: false
|
|
202
|
+
requirements:
|
|
203
|
+
- - ! '>='
|
|
204
|
+
- !ruby/object:Gem::Version
|
|
205
|
+
version: '0'
|
|
206
|
+
requirements: []
|
|
207
|
+
rubyforge_project:
|
|
208
|
+
rubygems_version: 1.8.23
|
|
209
|
+
signing_key:
|
|
210
|
+
specification_version: 3
|
|
211
|
+
summary: TestMyCode Commandline client
|
|
212
|
+
test_files: []
|