et 0.5.5 → 0.5.6.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/bin/et +1 -1
- data/lib/et.rb +10 -8
- data/lib/et/api.rb +35 -23
- data/lib/et/archive_manager.rb +49 -0
- data/lib/et/lesson.rb +20 -12
- data/lib/et/operating_system.rb +37 -0
- data/lib/et/runner.rb +6 -5
- data/lib/et/version.rb +1 -1
- data/spec/lib/api_spec.rb +83 -5
- data/spec/lib/archive_manager_spec.rb +27 -0
- data/spec/lib/lesson_spec.rb +24 -0
- data/spec/lib/operating_system_spec.rb +20 -0
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2782275fe3bc5bae13bec94ba826d026cb9e7c0
|
4
|
+
data.tar.gz: 0e65bdf3385e33a0073aac29dbd10607227455e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7e91173a2eb580bd11ddfc80da3f05d7c78ce533c176722fcf5d16cd0f6dfffde7da84f1f05f96c08261bb29cc81f071fbbae54e10b036f7dd0bcbeb0e45fef
|
7
|
+
data.tar.gz: a9f8c1d3ca5ad1f8c4130f1670d5b07b84e5896b249804c9e5375af7da3966febb74a2c7717d9ad7b0971546ec2acfd896aee1d4268c7b7ab5657b4e39b93f73
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.2.
|
1
|
+
2.2.3
|
data/bin/et
CHANGED
data/lib/et.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require "tmpdir"
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
require_relative "et/version"
|
3
|
+
require_relative "et/runner"
|
4
|
+
require_relative "et/operating_system"
|
5
|
+
require_relative "et/api"
|
6
|
+
require_relative "et/config"
|
7
|
+
require_relative "et/formatter"
|
8
|
+
require_relative "et/lesson"
|
9
|
+
require_relative "et/challenge"
|
10
|
+
require_relative "et/exercise"
|
11
|
+
require_relative "et/archive_manager"
|
data/lib/et/api.rb
CHANGED
@@ -2,6 +2,7 @@ require "net/http/post/multipart"
|
|
2
2
|
require "securerandom"
|
3
3
|
require "base64"
|
4
4
|
require "json"
|
5
|
+
require "openssl"
|
5
6
|
|
6
7
|
module ET
|
7
8
|
class API
|
@@ -17,12 +18,7 @@ module ET
|
|
17
18
|
request = Net::HTTP::Get.new(lessons_url)
|
18
19
|
request["Authorization"] = auth_header
|
19
20
|
|
20
|
-
response =
|
21
|
-
Net::HTTP.start(lessons_url.host, lessons_url.port,
|
22
|
-
use_ssl: lessons_url.scheme == "https") do |http|
|
23
|
-
|
24
|
-
response = http.request(request)
|
25
|
-
end
|
21
|
+
response = issue_request(request)
|
26
22
|
JSON.parse(response.body, symbolize_names: true)[:lessons]
|
27
23
|
end
|
28
24
|
|
@@ -30,12 +26,8 @@ module ET
|
|
30
26
|
request = Net::HTTP::Get.new(lesson_url(slug))
|
31
27
|
request["Authorization"] = auth_header
|
32
28
|
|
33
|
-
response =
|
34
|
-
Net::HTTP.start(lessons_url.host, lessons_url.port,
|
35
|
-
use_ssl: lessons_url.scheme == "https") do |http|
|
29
|
+
response = issue_request(request)
|
36
30
|
|
37
|
-
response = http.request(request)
|
38
|
-
end
|
39
31
|
body = JSON.parse(response.body, symbolize_names: true)
|
40
32
|
body[:lesson]
|
41
33
|
end
|
@@ -44,17 +36,16 @@ module ET
|
|
44
36
|
uri = URI(url)
|
45
37
|
dest = random_filename
|
46
38
|
|
47
|
-
Net::HTTP.
|
48
|
-
|
49
|
-
|
50
|
-
resp = http.get(uri.path)
|
51
|
-
|
39
|
+
request = Net::HTTP::Get.new(uri.path)
|
40
|
+
response = issue_request(request, url)
|
41
|
+
if response.code == "200"
|
52
42
|
open(dest, 'wb') do |file|
|
53
|
-
file.write(
|
43
|
+
file.write(response.body)
|
54
44
|
end
|
45
|
+
dest
|
46
|
+
else
|
47
|
+
nil
|
55
48
|
end
|
56
|
-
|
57
|
-
dest
|
58
49
|
end
|
59
50
|
|
60
51
|
def submit_lesson(lesson)
|
@@ -66,16 +57,33 @@ module ET
|
|
66
57
|
"submission[archive]" => UploadIO.new(f, "application/x-tar", "archive.tar.gz"))
|
67
58
|
request["Authorization"] = auth_header
|
68
59
|
|
69
|
-
|
70
|
-
|
60
|
+
issue_request(request)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
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|
|
71
70
|
|
72
71
|
http.request(request)
|
73
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
|
74
84
|
end
|
75
85
|
end
|
76
86
|
|
77
|
-
private
|
78
|
-
|
79
87
|
def lesson_url(slug)
|
80
88
|
URI.join(host, "lessons/#{slug}.json?submittable=1")
|
81
89
|
end
|
@@ -99,5 +107,9 @@ module ET
|
|
99
107
|
def auth_header
|
100
108
|
"Basic #{credentials}"
|
101
109
|
end
|
110
|
+
|
111
|
+
def operating_system
|
112
|
+
@os ||= ET::OperatingSystem.new
|
113
|
+
end
|
102
114
|
end
|
103
115
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "rubygems/package"
|
2
|
+
require "pry"
|
3
|
+
|
4
|
+
class ET::ArchiveManager
|
5
|
+
attr_reader :archive, :destination, :unpacked_files
|
6
|
+
|
7
|
+
def initialize(archive, destination)
|
8
|
+
@archive = archive
|
9
|
+
@destination = destination
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete_archive
|
13
|
+
File.delete(archive)
|
14
|
+
end
|
15
|
+
|
16
|
+
def unpack
|
17
|
+
@unpacked_files = []
|
18
|
+
File.open(archive, "rb") do |tar_gz|
|
19
|
+
uncompress(tar_gz)
|
20
|
+
end
|
21
|
+
@unpacked_files
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def uncompress(file)
|
27
|
+
Zlib::GzipReader.open(file) do |tar|
|
28
|
+
process_tar(tar)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_tar(tar)
|
33
|
+
Gem::Package::TarReader.new(tar) do |entries|
|
34
|
+
entries.each { |entry| create_file(entry) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_file(entry)
|
39
|
+
if entry.file?
|
40
|
+
filename = File.join(destination, entry.full_name)
|
41
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
42
|
+
File.open(filename, "wb") do |f|
|
43
|
+
f.write(entry.read)
|
44
|
+
end
|
45
|
+
File.chmod(entry.header.mode, filename)
|
46
|
+
@unpacked_files << filename
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/et/lesson.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "pathname"
|
2
2
|
require "securerandom"
|
3
|
+
require "rubygems/package"
|
3
4
|
|
4
5
|
module ET
|
5
6
|
class Lesson
|
@@ -13,19 +14,26 @@ module ET
|
|
13
14
|
if exists?
|
14
15
|
filepath = random_archive_path
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
File.open(filepath, "wb") do |file|
|
18
|
+
Zlib::GzipWriter.wrap(file) do |gz|
|
19
|
+
Gem::Package::TarWriter.new(gz) do |tar|
|
20
|
+
Dir.glob(File.join(dir, "**/*")).each do |file|
|
21
|
+
relative_path = file.gsub(dir + "/", "")
|
22
|
+
if !ignored_files.include?(relative_path)
|
23
|
+
if FileTest.directory?(file)
|
24
|
+
tar.mkdir(relative_path, 0755)
|
25
|
+
else
|
26
|
+
file_contents = File.read(file)
|
27
|
+
tar.add_file_simple("./" + relative_path, 0555, file_contents.length) do |io|
|
28
|
+
io.write(file_contents)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
28
35
|
end
|
36
|
+
filepath
|
29
37
|
else
|
30
38
|
nil
|
31
39
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
module ET
|
4
|
+
class OperatingSystem
|
5
|
+
attr_reader :raw_name, :version
|
6
|
+
|
7
|
+
def initialize(os = nil, version = nil)
|
8
|
+
@raw_name, @version = os, version
|
9
|
+
if @raw_name.nil?
|
10
|
+
derive_raw_name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
#cribbed from Selenium
|
15
|
+
def name
|
16
|
+
case @raw_name
|
17
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
18
|
+
:windows
|
19
|
+
when /darwin|mac os/
|
20
|
+
:mac
|
21
|
+
when /linux/
|
22
|
+
:linux
|
23
|
+
when /solaris|bsd/
|
24
|
+
:unix
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def platform_family?(platform)
|
29
|
+
name == platform.to_sym
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
def derive_raw_name
|
34
|
+
@raw_name = RbConfig::CONFIG['host_os']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/et/runner.rb
CHANGED
@@ -50,13 +50,14 @@ module ET
|
|
50
50
|
cmdargs.each do |slug|
|
51
51
|
lesson = api.get_lesson(slug)
|
52
52
|
archive = api.download_file(lesson[:archive_url])
|
53
|
+
archive_manager = ET::ArchiveManager.new(archive, cwd)
|
54
|
+
archive_manager.unpack
|
53
55
|
|
54
|
-
if
|
55
|
-
|
56
|
-
|
57
|
-
puts "Extracted challenge to #{challenge_dir}"
|
56
|
+
if !archive_manager.unpacked_files.empty?
|
57
|
+
archive_manager.delete_archive
|
58
|
+
puts "'#{slug}' extracted to '#{archive_manager.destination}'"
|
58
59
|
else
|
59
|
-
raise StandardError.new("Failed to extract the
|
60
|
+
raise StandardError.new("Failed to extract the archive.")
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
data/lib/et/version.rb
CHANGED
data/spec/lib/api_spec.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
describe ET::API do
|
2
2
|
let(:api) { ET::API.new(host: "http://localhost:3000") }
|
3
|
+
let(:lessons_uri) do
|
4
|
+
URI("http://localhost:3000/lessons.json?submittable=1")
|
5
|
+
end
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
7
|
+
let(:lessons_response) do
|
8
|
+
File.read("spec/data/lessons.json")
|
9
|
+
end
|
8
10
|
|
11
|
+
describe "lessons" do
|
9
12
|
it "queries for a list of lessons" do
|
10
13
|
request = {}
|
11
14
|
response = double
|
12
15
|
http = double
|
13
|
-
lessons_uri = URI("http://localhost:3000/lessons.json?submittable=1")
|
14
16
|
expect(Net::HTTP::Get).to receive(:new).
|
15
17
|
with(lessons_uri).
|
16
18
|
and_return(request)
|
@@ -38,6 +40,7 @@ describe ET::API do
|
|
38
40
|
request = {}
|
39
41
|
response = double
|
40
42
|
http = double
|
43
|
+
|
41
44
|
lesson_uri = URI("http://localhost:3000/lessons/rock-paper-scissors.json?submittable=1")
|
42
45
|
expect(Net::HTTP::Get).to receive(:new).
|
43
46
|
with(lesson_uri).
|
@@ -56,4 +59,79 @@ describe ET::API do
|
|
56
59
|
expect(result[:archive_url]).to eq("http://example.com/rock-paper-scissors.tar.gz")
|
57
60
|
end
|
58
61
|
end
|
62
|
+
|
63
|
+
context 'ssl verification' do
|
64
|
+
it 're-raises an exception for non Windows machines' do
|
65
|
+
dbl_os = double
|
66
|
+
allow(dbl_os).to receive(:platform_family?).with(:windows).and_return(false)
|
67
|
+
expect(ET::OperatingSystem).to receive(:new).and_return(dbl_os)
|
68
|
+
|
69
|
+
expect(Net::HTTP).to receive(:start).and_raise(OpenSSL::SSL::SSLError)
|
70
|
+
expect{ api.list_lessons }.to raise_error(OpenSSL::SSL::SSLError)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'swallows the exception for windows machines and reissues' do
|
74
|
+
http = double
|
75
|
+
allow(http).to receive(:start).and_yield(http)
|
76
|
+
allow(http).to receive(:verify_mode=)
|
77
|
+
allow(http).to receive(:use_ssl=)
|
78
|
+
response = double
|
79
|
+
|
80
|
+
allow(http).to receive(:request).and_return(response)
|
81
|
+
allow(response).to receive(:body).and_return(lessons_response)
|
82
|
+
|
83
|
+
allow(Net::HTTP).to receive(:start).
|
84
|
+
with(
|
85
|
+
lessons_uri.host,
|
86
|
+
lessons_uri.port,
|
87
|
+
use_ssl: lessons_uri.scheme == 'https').
|
88
|
+
and_raise(OpenSSL::SSL::SSLError)
|
89
|
+
|
90
|
+
expect(Net::HTTP).to receive(:new).with(
|
91
|
+
lessons_uri.host,
|
92
|
+
lessons_uri.port).
|
93
|
+
and_return(http)
|
94
|
+
|
95
|
+
dbl_os = double
|
96
|
+
allow(dbl_os).to receive(:platform_family?).and_return(:windows)
|
97
|
+
allow(ET::OperatingSystem).to receive(:new).and_return(dbl_os)
|
98
|
+
|
99
|
+
api.list_lessons
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'downloading files' do
|
104
|
+
it 'returns nil when a 404 is encountered' do
|
105
|
+
|
106
|
+
http = double
|
107
|
+
response = double
|
108
|
+
allow(response).to receive(:code).and_return("404")
|
109
|
+
allow(http).to receive(:request).and_return(response)
|
110
|
+
allow(Net::HTTP).to receive(:start).and_yield(http)
|
111
|
+
|
112
|
+
expect(api.download_file("http://example.com/somefile.tar.gz")).to be_nil
|
113
|
+
end
|
114
|
+
it 'returns a local file when a challenge is successfully downloaded' do
|
115
|
+
path = '/tmp/et'
|
116
|
+
filename = 'fab'
|
117
|
+
|
118
|
+
FileUtils.rm_rf(File.join(path, filename))
|
119
|
+
FileUtils.mkdir_p(path)
|
120
|
+
|
121
|
+
allow(Dir).to receive(:mktmpdir).and_return(path)
|
122
|
+
allow(SecureRandom).to receive(:hex).and_return(filename)
|
123
|
+
|
124
|
+
http = double
|
125
|
+
response = double
|
126
|
+
file_contents = File.read(File.join(File.dirname(__FILE__), "../data/some-challenge.tar.gz"))
|
127
|
+
allow(response).to receive(:body).and_return(file_contents)
|
128
|
+
allow(response).to receive(:code).and_return("200")
|
129
|
+
allow(http).to receive(:request).and_return(response)
|
130
|
+
allow(Net::HTTP).to receive(:start).and_yield(http)
|
131
|
+
|
132
|
+
url = 'http://example.com/some-challenge.tar.gz'
|
133
|
+
|
134
|
+
expect(api.download_file(url)).to eql(File.join(path, filename))
|
135
|
+
end
|
136
|
+
end
|
59
137
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
describe ET::ArchiveManager do
|
2
|
+
let!(:archive) { File.join(File.dirname(__FILE__), "..", "data", "some-challenge.tar.gz") }
|
3
|
+
let!(:destination) { Dir.mktmpdir }
|
4
|
+
let!(:lesson_extractor) { ET::ArchiveManager.new(archive, destination) }
|
5
|
+
|
6
|
+
describe "#new" do
|
7
|
+
it "takes an archive and destination" do
|
8
|
+
expect(lesson_extractor).to be_a(ET::ArchiveManager)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#unpack" do
|
13
|
+
it "extracts files from the archive" do
|
14
|
+
lesson_extractor.unpack
|
15
|
+
extracted_files = Dir.entries(File.join(destination, "some-challenge"))
|
16
|
+
expect(extracted_files).to include("README.md")
|
17
|
+
expect(extracted_files).to include("sample.rb")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns an array of extracted filenames" do
|
21
|
+
result = lesson_extractor.unpack
|
22
|
+
expect(result).to be_an(Array)
|
23
|
+
expect(result.size).to eq(2)
|
24
|
+
expect(result.first).to match /sample.rb/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ET::Lesson do
|
4
|
+
context "archive! method" do
|
5
|
+
it "creates a tar.gz file" do
|
6
|
+
path = '/tmp/et'
|
7
|
+
filename = 'fab'
|
8
|
+
|
9
|
+
FileUtils.rm_rf(File.join(path, filename))
|
10
|
+
FileUtils.mkdir_p(path)
|
11
|
+
|
12
|
+
allow(Dir).to receive(:mktmpdir).and_return(path)
|
13
|
+
allow(SecureRandom).to receive(:hex).and_return(filename)
|
14
|
+
|
15
|
+
exercise_path = File.expand_path(
|
16
|
+
File.join(File.dirname(__FILE__), '../data/sample-exercise'))
|
17
|
+
lesson = ET::Lesson.new(exercise_path)
|
18
|
+
resulting_file = lesson.archive!
|
19
|
+
|
20
|
+
expect(resulting_file).to_not be_nil
|
21
|
+
expect(FileTest).to exist(resulting_file)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
describe ET::OperatingSystem do
|
2
|
+
context '10.10' do
|
3
|
+
let(:minor) { '10.10' }
|
4
|
+
let(:os) { ET::OperatingSystem.new('darwin', minor) }
|
5
|
+
|
6
|
+
it 'has a name' do
|
7
|
+
expect(os.name).to eq(:mac)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
context '10.9' do
|
13
|
+
let(:minor) { '10.9' }
|
14
|
+
let(:os) { ET::OperatingSystem.new('darwin', minor + '.4') }
|
15
|
+
|
16
|
+
it 'has a minor version' do
|
17
|
+
expect(os.name).to eq(:mac)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: et
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Sheehan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -116,11 +116,13 @@ files:
|
|
116
116
|
- et.gemspec
|
117
117
|
- lib/et.rb
|
118
118
|
- lib/et/api.rb
|
119
|
+
- lib/et/archive_manager.rb
|
119
120
|
- lib/et/challenge.rb
|
120
121
|
- lib/et/config.rb
|
121
122
|
- lib/et/exercise.rb
|
122
123
|
- lib/et/formatter.rb
|
123
124
|
- lib/et/lesson.rb
|
125
|
+
- lib/et/operating_system.rb
|
124
126
|
- lib/et/runner.rb
|
125
127
|
- lib/et/version.rb
|
126
128
|
- spec/cli/get_lesson_spec.rb
|
@@ -139,9 +141,12 @@ files:
|
|
139
141
|
- spec/data/sample-exercise/test/sample_exercise_test.rb
|
140
142
|
- spec/data/some-challenge.tar.gz
|
141
143
|
- spec/lib/api_spec.rb
|
144
|
+
- spec/lib/archive_manager_spec.rb
|
142
145
|
- spec/lib/challenge_spec.rb
|
143
146
|
- spec/lib/config_spec.rb
|
144
147
|
- spec/lib/exercise_spec.rb
|
148
|
+
- spec/lib/lesson_spec.rb
|
149
|
+
- spec/lib/operating_system_spec.rb
|
145
150
|
- spec/spec_helper.rb
|
146
151
|
- spec/support/helpers/archive_helper.rb
|
147
152
|
- spec/support/helpers/output_catcher.rb
|
@@ -163,12 +168,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
163
168
|
version: '0'
|
164
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
170
|
requirements:
|
166
|
-
- - "
|
171
|
+
- - ">"
|
167
172
|
- !ruby/object:Gem::Version
|
168
|
-
version:
|
173
|
+
version: 1.3.1
|
169
174
|
requirements: []
|
170
175
|
rubyforge_project:
|
171
|
-
rubygems_version: 2.
|
176
|
+
rubygems_version: 2.4.5.1
|
172
177
|
signing_key:
|
173
178
|
specification_version: 4
|
174
179
|
summary: Command-line interface for the event horizon.
|