et 0.5.9 → 0.7.3
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 +5 -5
- data/.github/workflows/ruby.yml +30 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +456 -0
- data/.ruby-version +1 -1
- data/Gemfile.lock +27 -23
- data/README.md +17 -0
- data/et.gemspec +3 -2
- data/lib/et.rb +1 -2
- data/lib/et/api.rb +40 -60
- data/lib/et/fallback_connection.rb +29 -0
- data/lib/et/lesson.rb +12 -20
- data/lib/et/runner.rb +2 -16
- data/lib/et/submission_file_list.rb +58 -0
- data/lib/et/version.rb +1 -1
- data/spec/cli/get_lesson_spec.rb +14 -14
- data/spec/cli/list_lessons_spec.rb +1 -1
- data/spec/data/.et +1 -0
- data/spec/data/bloated-challenge/.etignore +4 -0
- data/spec/data/bloated-challenge/bloated-challenge.md +3 -0
- data/spec/data/bloated-challenge/node_modules/boo/somefile.js +1 -0
- data/spec/data/bloated-challenge/problem.rb +1 -0
- data/spec/data/bloated-challenge/rando_folder/rando_file.js +1 -0
- data/spec/data/sample-challenge/.etignore +1 -0
- data/spec/data/sample-exercise/.etignore +1 -0
- data/spec/lib/api_spec.rb +54 -88
- data/spec/lib/fallback_connection_spec.rb +42 -0
- data/spec/lib/lesson_spec.rb +1 -1
- data/spec/lib/submission_file_list_spec.rb +31 -0
- data/spec/spec_helper.rb +0 -5
- metadata +56 -21
- data/lib/et/challenge.rb +0 -7
- data/lib/et/exercise.rb +0 -15
- data/spec/cli/run_exercise_test_suite_spec.rb +0 -31
- data/spec/data/sample-challenge/.lesson.yml +0 -6
- data/spec/data/sample-exercise/.lesson.yml +0 -6
- data/spec/lib/challenge_spec.rb +0 -78
- data/spec/lib/exercise_spec.rb +0 -28
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.3
|
data/Gemfile.lock
CHANGED
@@ -1,39 +1,44 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
et (0.
|
4
|
+
et (0.7.2)
|
5
|
+
faraday (~> 0.9)
|
6
|
+
faraday_middleware (~> 0.10)
|
5
7
|
gli (= 2.11.0)
|
6
8
|
multipart-post (~> 2.0)
|
9
|
+
rake (~> 10)
|
7
10
|
rspec (~> 3.0)
|
8
11
|
rspec-mocks (~> 3.0)
|
9
12
|
|
10
13
|
GEM
|
11
14
|
remote: https://rubygems.org/
|
12
15
|
specs:
|
13
|
-
coderay (1.1.
|
14
|
-
diff-lcs (1.
|
16
|
+
coderay (1.1.2)
|
17
|
+
diff-lcs (1.3)
|
18
|
+
faraday (0.17.3)
|
19
|
+
multipart-post (>= 1.2, < 3)
|
20
|
+
faraday_middleware (0.14.0)
|
21
|
+
faraday (>= 0.7.4, < 1.0)
|
15
22
|
gli (2.11.0)
|
16
|
-
method_source (0.
|
17
|
-
multipart-post (2.
|
18
|
-
pry (0.
|
19
|
-
coderay (~> 1.1
|
20
|
-
method_source (~> 0
|
21
|
-
slop (~> 3.4)
|
23
|
+
method_source (1.0.0)
|
24
|
+
multipart-post (2.1.1)
|
25
|
+
pry (0.13.1)
|
26
|
+
coderay (~> 1.1)
|
27
|
+
method_source (~> 1.0)
|
22
28
|
rake (10.5.0)
|
23
|
-
rspec (3.
|
24
|
-
rspec-core (~> 3.
|
25
|
-
rspec-expectations (~> 3.
|
26
|
-
rspec-mocks (~> 3.
|
27
|
-
rspec-core (3.
|
28
|
-
rspec-support (~> 3.
|
29
|
-
rspec-expectations (3.
|
29
|
+
rspec (3.9.0)
|
30
|
+
rspec-core (~> 3.9.0)
|
31
|
+
rspec-expectations (~> 3.9.0)
|
32
|
+
rspec-mocks (~> 3.9.0)
|
33
|
+
rspec-core (3.9.2)
|
34
|
+
rspec-support (~> 3.9.3)
|
35
|
+
rspec-expectations (3.9.2)
|
30
36
|
diff-lcs (>= 1.2.0, < 2.0)
|
31
|
-
rspec-support (~> 3.
|
32
|
-
rspec-mocks (3.
|
37
|
+
rspec-support (~> 3.9.0)
|
38
|
+
rspec-mocks (3.9.1)
|
33
39
|
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
-
rspec-support (~> 3.
|
35
|
-
rspec-support (3.
|
36
|
-
slop (3.6.0)
|
40
|
+
rspec-support (~> 3.9.0)
|
41
|
+
rspec-support (3.9.3)
|
37
42
|
|
38
43
|
PLATFORMS
|
39
44
|
ruby
|
@@ -41,7 +46,6 @@ PLATFORMS
|
|
41
46
|
DEPENDENCIES
|
42
47
|
et!
|
43
48
|
pry (~> 0)
|
44
|
-
rake (~> 10)
|
45
49
|
|
46
50
|
BUNDLED WITH
|
47
|
-
1.
|
51
|
+
1.17.2
|
data/README.md
CHANGED
@@ -28,3 +28,20 @@ COMMANDS
|
|
28
28
|
submit - Submit the lesson in this directory.
|
29
29
|
test - Run an exercise test suite.
|
30
30
|
```
|
31
|
+
|
32
|
+
### Releasing New Versions
|
33
|
+
|
34
|
+
Bundler provided `gem_tasks` have been incorporated into this libraries
|
35
|
+
`Rakefile`. Thankfully these tasks make releasing new gem versions a snap!
|
36
|
+
|
37
|
+
To release a new version of this gem:
|
38
|
+
|
39
|
+
1. Bump the version according to [semantic versioning](http://semver.org/)
|
40
|
+
2. Perform a git commit
|
41
|
+
3. Run `rake release` from the project root
|
42
|
+
|
43
|
+
Bundler's provided rake task will appropriately push a tag to GitHub and the gem
|
44
|
+
itself to [rubygems.org](https://rubygems.org)
|
45
|
+
|
46
|
+
_Note:_ in order to release the gem you must be an authorized owner of it on
|
47
|
+
[rubygems.org](https://rubygems.org)
|
data/et.gemspec
CHANGED
@@ -15,14 +15,15 @@ DESC
|
|
15
15
|
|
16
16
|
s.files = `git ls-files`.split("\n")
|
17
17
|
s.require_paths << "lib"
|
18
|
-
s.has_rdoc = false
|
19
18
|
s.bindir = "bin"
|
20
19
|
s.executables << "et"
|
21
20
|
s.license = "MIT"
|
22
|
-
s.add_development_dependency("rake", "~> 10")
|
23
21
|
s.add_development_dependency("pry", '~> 0')
|
24
22
|
s.add_runtime_dependency("rspec", "~> 3.0")
|
23
|
+
s.add_runtime_dependency("rake", "~> 10")
|
25
24
|
s.add_runtime_dependency("rspec-mocks", "~> 3.0")
|
26
25
|
s.add_runtime_dependency("multipart-post", "~> 2.0")
|
27
26
|
s.add_runtime_dependency("gli", "2.11.0")
|
27
|
+
s.add_runtime_dependency("faraday", "~> 0.9")
|
28
|
+
s.add_runtime_dependency("faraday_middleware", "~> 0.10")
|
28
29
|
end
|
data/lib/et.rb
CHANGED
@@ -3,9 +3,8 @@ require_relative "et/version"
|
|
3
3
|
require_relative "et/runner"
|
4
4
|
require_relative "et/operating_system"
|
5
5
|
require_relative "et/api"
|
6
|
+
require_relative "et/submission_file_list"
|
6
7
|
require_relative "et/config"
|
7
8
|
require_relative "et/formatter"
|
8
9
|
require_relative "et/lesson"
|
9
|
-
require_relative "et/challenge"
|
10
|
-
require_relative "et/exercise"
|
11
10
|
require_relative "et/archive_manager"
|
data/lib/et/api.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require "net/http/post/multipart"
|
2
1
|
require "securerandom"
|
3
2
|
require "base64"
|
4
3
|
require "json"
|
5
4
|
require "openssl"
|
6
5
|
|
6
|
+
require_relative "fallback_connection"
|
7
7
|
module ET
|
8
8
|
class API
|
9
9
|
attr_reader :host, :username, :token
|
@@ -15,30 +15,29 @@ module ET
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def list_lessons
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
response = nil
|
19
|
+
api_client.open do |client|
|
20
|
+
response = client.get('/lessons.json', :submittable => 1)
|
21
|
+
end
|
22
|
+
response.body['lessons']
|
23
23
|
end
|
24
24
|
|
25
25
|
def get_lesson(slug)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
body = JSON.parse(response.body, symbolize_names: true)
|
32
|
-
body[:lesson]
|
26
|
+
resp = nil
|
27
|
+
api_client.open do |client|
|
28
|
+
resp = client.get(lesson_url(slug), :submittable => 1)
|
29
|
+
end
|
30
|
+
resp.body['lesson']
|
33
31
|
end
|
34
32
|
|
35
33
|
def download_file(url)
|
36
|
-
|
34
|
+
response = nil
|
37
35
|
dest = random_filename
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
download_client(url).open do |client|
|
38
|
+
response = client.get(URI(url).path)
|
39
|
+
end
|
40
|
+
if response.status == 200
|
42
41
|
open(dest, 'wb') do |file|
|
43
42
|
file.write(response.body)
|
44
43
|
end
|
@@ -50,66 +49,47 @@ module ET
|
|
50
49
|
|
51
50
|
def submit_lesson(lesson)
|
52
51
|
submission_file = lesson.archive!
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
"submission
|
58
|
-
request["Authorization"] = auth_header
|
59
|
-
|
60
|
-
issue_request(request)
|
52
|
+
io = Faraday::UploadIO.new(submission_file, "application/x-tar")
|
53
|
+
resp = nil
|
54
|
+
api_client.open do |client|
|
55
|
+
resp = client.post(submission_url(lesson.slug),
|
56
|
+
"submission" => { "archive" => io})
|
61
57
|
end
|
58
|
+
resp
|
62
59
|
end
|
63
60
|
|
64
61
|
private
|
65
|
-
def issue_request(request, url = nil)
|
66
|
-
uri = URI.parse(url || @host)
|
67
|
-
begin
|
68
|
-
Net::HTTP.start(uri.host, uri.port,
|
69
|
-
use_ssl: uri.scheme == "https") do |http|
|
70
|
-
|
71
|
-
http.request(request)
|
72
|
-
end
|
73
|
-
rescue OpenSSL::SSL::SSLError => e
|
74
|
-
if operating_system.platform_family?(:windows)
|
75
|
-
https = Net::HTTP.new(uri.host, uri.port)
|
76
|
-
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
77
|
-
https.use_ssl = uri.scheme == 'https'
|
78
|
-
https.start do |http|
|
79
|
-
http.request(request)
|
80
|
-
end
|
81
|
-
else
|
82
|
-
raise e
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
62
|
def lesson_url(slug)
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
def lessons_url
|
92
|
-
URI.join(host, "lessons.json?submittable=1")
|
63
|
+
"/lessons/#{slug}.json"
|
93
64
|
end
|
94
65
|
|
95
66
|
def submission_url(slug)
|
96
|
-
|
67
|
+
"/lessons/#{slug}/submissions.json"
|
97
68
|
end
|
98
69
|
|
99
70
|
def random_filename
|
100
71
|
File.join(Dir.mktmpdir, SecureRandom.hex)
|
101
72
|
end
|
102
73
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
74
|
+
def api_client
|
75
|
+
@api_client ||= ET::FallbackConnection.new(:url => @host) do |client|
|
76
|
+
client.request :multipart
|
106
77
|
|
107
|
-
|
108
|
-
|
78
|
+
client.request :url_encoded
|
79
|
+
client.request :basic_auth, username, token
|
80
|
+
|
81
|
+
client.response :json, :content_type => /\bjson$/
|
82
|
+
|
83
|
+
client.adapter Faraday.default_adapter
|
84
|
+
end
|
109
85
|
end
|
110
86
|
|
111
|
-
def
|
112
|
-
|
87
|
+
def download_client(url)
|
88
|
+
uri = URI(url)
|
89
|
+
scheme_and_host = [uri.scheme, uri.host].join('://')
|
90
|
+
ET::FallbackConnection.new(:url => scheme_and_host) do |client|
|
91
|
+
client.adapter Faraday.default_adapter
|
92
|
+
end
|
113
93
|
end
|
114
94
|
end
|
115
95
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "faraday_middleware"
|
3
|
+
|
4
|
+
module ET
|
5
|
+
class FallbackConnection
|
6
|
+
def initialize(opts = {}, &block)
|
7
|
+
@connection = Faraday.new(opts, &block)
|
8
|
+
|
9
|
+
@fallback_connection = Faraday.new(opts.merge(:ssl => {:verify => false}), &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def open(&block)
|
13
|
+
begin
|
14
|
+
block.call(@connection)
|
15
|
+
rescue Faraday::SSLError => e
|
16
|
+
if operating_system.platform_family?(:windows)
|
17
|
+
block.call(@fallback_connection)
|
18
|
+
else
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def operating_system
|
26
|
+
@os ||= ET::OperatingSystem.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/et/lesson.rb
CHANGED
@@ -17,16 +17,16 @@ module ET
|
|
17
17
|
File.open(filepath, "wb") do |file|
|
18
18
|
Zlib::GzipWriter.wrap(file) do |gz|
|
19
19
|
Gem::Package::TarWriter.new(gz) do |tar|
|
20
|
-
|
21
|
-
relative_path = file
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
ET::SubmissionFileList.new(dir).each do |file|
|
21
|
+
relative_path = file
|
22
|
+
absolute_path = File.join(dir, file)
|
23
|
+
|
24
|
+
if FileTest.directory?(absolute_path)
|
25
|
+
tar.mkdir(relative_path, 0755)
|
26
|
+
else
|
27
|
+
file_contents = File.read(absolute_path)
|
28
|
+
tar.add_file_simple("./" + relative_path, 0555, file_contents.bytesize) do |io|
|
29
|
+
io.write(file_contents)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -51,24 +51,16 @@ module ET
|
|
51
51
|
!dir.nil?
|
52
52
|
end
|
53
53
|
|
54
|
-
def ignored_files
|
55
|
-
(config["ignore"] || []) + [".lesson.yml"]
|
56
|
-
end
|
57
|
-
|
58
54
|
protected
|
59
55
|
|
60
|
-
def config
|
61
|
-
@config ||= YAML.load_file(File.join(dir, ".lesson.yml"))
|
62
|
-
end
|
63
|
-
|
64
56
|
def random_archive_path
|
65
57
|
File.join(Dir.mktmpdir, "#{SecureRandom.hex}.tar.gz")
|
66
58
|
end
|
67
59
|
|
68
60
|
def find_lesson_dir(current_dir)
|
69
|
-
|
61
|
+
parent_directory = File.dirname(current_dir)
|
70
62
|
|
71
|
-
if
|
63
|
+
if Config.new(parent_directory).exists?
|
72
64
|
current_dir
|
73
65
|
elsif current_dir == "." || Pathname.new(current_dir).root?
|
74
66
|
nil
|
data/lib/et/runner.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "gli"
|
2
|
-
require "yaml"
|
3
2
|
|
4
3
|
module ET
|
5
4
|
class Runner
|
@@ -40,7 +39,7 @@ module ET
|
|
40
39
|
desc "List available lessons."
|
41
40
|
command :list do |c|
|
42
41
|
c.action do |_global_options, _options, _cmdargs|
|
43
|
-
Formatter.print_table(api.list_lessons,
|
42
|
+
Formatter.print_table(api.list_lessons, 'slug', 'title', 'type')
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
@@ -49,7 +48,7 @@ module ET
|
|
49
48
|
c.action do |_global_options, _options, cmdargs|
|
50
49
|
cmdargs.each do |slug|
|
51
50
|
lesson = api.get_lesson(slug)
|
52
|
-
archive = api.download_file(lesson[
|
51
|
+
archive = api.download_file(lesson['archive_url'])
|
53
52
|
archive_manager = ET::ArchiveManager.new(archive, cwd)
|
54
53
|
archive_manager.unpack
|
55
54
|
|
@@ -77,19 +76,6 @@ module ET
|
|
77
76
|
end
|
78
77
|
end
|
79
78
|
|
80
|
-
desc "Run an exercise test suite."
|
81
|
-
command :test do |c|
|
82
|
-
c.action do |_global_options, _options, _cmdargs|
|
83
|
-
exercise = Exercise.new(cwd)
|
84
|
-
|
85
|
-
if exercise.exists?
|
86
|
-
exercise.run_tests
|
87
|
-
else
|
88
|
-
raise StandardError.new("Not in an exercise directory.")
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
79
|
run(args)
|
94
80
|
end
|
95
81
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "rake/file_list"
|
2
|
+
|
3
|
+
module ET
|
4
|
+
class SubmissionFileList
|
5
|
+
include Enumerable
|
6
|
+
DEFAULT_IGNORE_GLOBS = [
|
7
|
+
'.etignore',
|
8
|
+
'node_modules/',
|
9
|
+
'coverage/'
|
10
|
+
]
|
11
|
+
|
12
|
+
def initialize(path)
|
13
|
+
@path = path
|
14
|
+
end
|
15
|
+
|
16
|
+
def files
|
17
|
+
unless @files
|
18
|
+
@files = Rake::FileList[File.join(@path, "**/*"), File.join(@path, ".etignore")]
|
19
|
+
ignore_globs.each do |glob|
|
20
|
+
filename = File.join(@path, glob)
|
21
|
+
|
22
|
+
if File.directory?(filename)
|
23
|
+
filename += glob.end_with?("/") ? "**/*" : "/**/*"
|
24
|
+
end
|
25
|
+
|
26
|
+
@files = @files.exclude(filename)
|
27
|
+
end
|
28
|
+
@files = @files.sub(File.join(@path, "/"), "")
|
29
|
+
end
|
30
|
+
|
31
|
+
@files
|
32
|
+
end
|
33
|
+
|
34
|
+
def each(&block)
|
35
|
+
files.each do |file|
|
36
|
+
block.call(file)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def ignore_globs
|
42
|
+
etignore_globs + [] + DEFAULT_IGNORE_GLOBS
|
43
|
+
end
|
44
|
+
|
45
|
+
def etignore_globs
|
46
|
+
unless @etignore_globs
|
47
|
+
etignore = File.join(@path, '.etignore')
|
48
|
+
if FileTest.exists?(etignore)
|
49
|
+
@etignore_globs = File.read(etignore).split(/\n/)
|
50
|
+
@etignore_globs.delete_if { | string | string.start_with?("#") || string.empty?}
|
51
|
+
else
|
52
|
+
@etignore_globs = []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@etignore_globs
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|