question_compiler 0.0.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/bin/qc +23 -0
- data/lib/question_compiler.rb +5 -0
- data/lib/question_compiler/command.rb +52 -0
- data/lib/question_compiler/compiler.rb +86 -0
- data/lib/question_compiler/local.rb +89 -0
- data/lib/question_compiler/server.rb +81 -0
- data/lib/question_compiler/version.rb +3 -0
- data/lib/question_compiler/zipper.rb +97 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6ba34bc5b728df960376e88dca9625b8f9b600e6
|
4
|
+
data.tar.gz: b40716c4e48cef949c7b6c60af5c1963246917c6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8d538f7526618e9820f0b9286e71aa71251ef6447d1b95bcbec6db7d5c40c65993ac833b1531cb6a44e3e59d837883159fa82e194dd66b304f95652fc45fcf34
|
7
|
+
data.tar.gz: 77b116b976c4ce7c026d984977565dfaf22435d9f12ccee73365503010a2f5ffe0020846b9d524495ea56e11f489acbddfb59fcf59727f184dee0cd24b1a217f
|
data/bin/qc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# When developing, run this script like so:
|
4
|
+
# DEV=1 ~/gems/question_compiler/bin/qc
|
5
|
+
|
6
|
+
if ENV['DEV'] == '1'
|
7
|
+
puts "\n"
|
8
|
+
puts '======================================'
|
9
|
+
puts 'Question compiler running in dev mode.'
|
10
|
+
puts '======================================'
|
11
|
+
puts "\n"
|
12
|
+
|
13
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
|
15
|
+
dev = true
|
16
|
+
else
|
17
|
+
dev = false
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'question_compiler'
|
21
|
+
require 'question_compiler/command'
|
22
|
+
|
23
|
+
QuestionCompiler::Command.new(ARGV.dup, dev).run
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'logger'
|
3
|
+
require 'slop'
|
4
|
+
require 'zip'
|
5
|
+
require 'question_compiler/local'
|
6
|
+
require 'question_compiler/zipper'
|
7
|
+
|
8
|
+
module QuestionCompiler
|
9
|
+
class Command
|
10
|
+
def initialize(args, dev = false)
|
11
|
+
@args = args
|
12
|
+
@dev = dev
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
opts = Slop.parse(@args) do |o|
|
17
|
+
o.bool '-z', '--zip', 'Builds zip files for all questions and contexts. Invoke from root folder.'
|
18
|
+
o.array '-c', '--compile', 'Builds the given questions Invoke from root folder. Usage: qc -c user1/qst1 user1/qst2 user2/qst3'
|
19
|
+
o.bool '-s', '--server', 'Starts a development webserver. Invoke from context or question folder.'
|
20
|
+
o.integer '-p', '--port', 'Webserver port.', default: 3000
|
21
|
+
o.on '-v', '--version', 'Prints the gem version.'
|
22
|
+
o.bool '-h', '--help', 'Prints this message.'
|
23
|
+
end
|
24
|
+
|
25
|
+
if opts.help?
|
26
|
+
puts opts
|
27
|
+
elsif opts.version?
|
28
|
+
puts QuestionCompiler::VERSION
|
29
|
+
elsif opts.compile?
|
30
|
+
log = Logger.new(STDOUT)
|
31
|
+
log.level = Logger::DEBUG if @dev
|
32
|
+
opts[:compile].each do |question_path|
|
33
|
+
username, _ = question_path.split('/')
|
34
|
+
FileUtils.mkdir_p File.join('compiled', question_path)
|
35
|
+
FileUtils.cp_r File.join('questions', question_path), File.join('compiled', username)
|
36
|
+
|
37
|
+
QuestionCompiler::Compiler.new(log).compile(
|
38
|
+
File.read(File.join('questions', question_path, 'question.html')),
|
39
|
+
QuestionCompiler::LocalContextReader.new('contexts'),
|
40
|
+
QuestionCompiler::LocalQuestionWriter.new('compiled', question_path)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
elsif opts.zip?
|
44
|
+
QuestionCompiler::Zipper.new.zip_all Dir.getwd
|
45
|
+
elsif opts.server?
|
46
|
+
QuestionCompiler::Server.start opts[:port]
|
47
|
+
else
|
48
|
+
puts opts
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module QuestionCompiler
|
2
|
+
class Compiler
|
3
|
+
attr_reader :context_path
|
4
|
+
|
5
|
+
def copy_dependencies(context_reader, question_writer, directory)
|
6
|
+
@log.debug "Copying dependencies in #{directory}"
|
7
|
+
context_reader.glob(
|
8
|
+
File.join(directory, '/**/*')
|
9
|
+
).reject do |dep_path|
|
10
|
+
!context_reader.file?(dep_path) or
|
11
|
+
File.basename(dep_path).start_with?('.')
|
12
|
+
end.each do |dep_path|
|
13
|
+
dep_path = dep_path.to_s
|
14
|
+
if dep_src = context_reader.read(dep_path)
|
15
|
+
question_writer.write dep_path, dep_src
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def copy_nodes(nodes_to_copy, dst_node, before)
|
21
|
+
@log.debug "Copying nodes [#{nodes_to_copy.map(&:name).join(', ')}] to #{dst_node.name}"
|
22
|
+
before = dst_node.at_css(before)
|
23
|
+
nodes_to_copy.each do |node|
|
24
|
+
if before
|
25
|
+
before.add_previous_sibling node
|
26
|
+
else
|
27
|
+
dst_node << node
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Note that `QuestionCompiler::Server` doesn't call this. Instead, it calls
|
33
|
+
# `#transform_question` directly.
|
34
|
+
def compile(question_src, context_reader, question_writer)
|
35
|
+
context_reader.log = @log
|
36
|
+
question_writer.log = @log
|
37
|
+
question_writer.open do
|
38
|
+
question_doc = Nokogiri.HTML question_src
|
39
|
+
question_doc = transform_question(question_doc, context_reader) do |context_path|
|
40
|
+
copy_dependencies context_reader, question_writer, 'assets'
|
41
|
+
end
|
42
|
+
|
43
|
+
question_writer.write 'question.html', question_doc.to_html
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(log)
|
48
|
+
@log = log
|
49
|
+
end
|
50
|
+
|
51
|
+
# `QuestionCompiler::Server` calls this directly, bypassing `#compile`.
|
52
|
+
def transform_question(question_doc, context_reader)
|
53
|
+
context_reader.log = @log
|
54
|
+
question_head = question_doc.at_css('head')
|
55
|
+
@log.debug 'Looking for an embedded context'
|
56
|
+
if embed_div = question_doc.css('div[data-embed-context]').first
|
57
|
+
@context_path = embed_div['data-embed-context']
|
58
|
+
@log.debug "Found embedded context: #{@context_path}"
|
59
|
+
|
60
|
+
context_reader.open(@context_path) do
|
61
|
+
if context_src = context_reader.read('context.html')
|
62
|
+
context_doc = Nokogiri.HTML context_src
|
63
|
+
|
64
|
+
if question_head
|
65
|
+
copy_nodes context_doc.css('head link'), question_head, 'link'
|
66
|
+
copy_nodes context_doc.css('head style'), question_head, 'style'
|
67
|
+
copy_nodes context_doc.css('head script'), question_head, 'script'
|
68
|
+
end
|
69
|
+
|
70
|
+
if context_body = context_doc.at_css('body')
|
71
|
+
embed_div.inner_html = ''
|
72
|
+
embed_div << context_body.inner_html
|
73
|
+
end
|
74
|
+
|
75
|
+
if block_given?
|
76
|
+
yield @context_path
|
77
|
+
end
|
78
|
+
else
|
79
|
+
@log.warn "Could not read context.html in #{@context_path}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
question_doc
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module QuestionCompiler
|
4
|
+
class LocalContextReader
|
5
|
+
# Given a context path such as `jarrett/example` and a glob such as `assets/*.css`,
|
6
|
+
# returns an array of matching paths. The returned paths are relative to the
|
7
|
+
# context folder.
|
8
|
+
def glob(glob_str)
|
9
|
+
Dir.glob(File.join(@root, @context_path, glob_str)).select do |sub_path|
|
10
|
+
File.file? sub_path
|
11
|
+
end.map do |sub_path|
|
12
|
+
sub_path.sub(File.join('contexts', @context_path), '')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def file?(sub_path)
|
17
|
+
File.file? File.join(@root, @context_path, sub_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Pass the path to the contexts folder. I.e. the folder whose children are usernames.
|
21
|
+
def initialize(root)
|
22
|
+
@root = root
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :log
|
26
|
+
|
27
|
+
# Pass a path such as `jarrett/example`.
|
28
|
+
def open(context_path)
|
29
|
+
@context_path = context_path
|
30
|
+
# This is basically a no-op. Some other context reader implementations may need to
|
31
|
+
# open and close the question as a whole.
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
|
35
|
+
# Given a path to a file within the context such as `assets/context.css`, returns the
|
36
|
+
# file data as a string. Returns nil if the file doesn't exist or is unreadable.
|
37
|
+
def read(sub_path)
|
38
|
+
path = File.expand_path(File.join(@root, @context_path, sub_path))
|
39
|
+
begin
|
40
|
+
data = File.read path
|
41
|
+
@log.debug "R: #{path} (#{data.length} bytes)"
|
42
|
+
data
|
43
|
+
rescue Exception
|
44
|
+
@log.warn "Could not read #{path}"
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class LocalQuestionWriter
|
51
|
+
# Pass the path to the compiled folder. I.e. the folder whose children are question names.
|
52
|
+
def initialize(root, question_path)
|
53
|
+
@root = root
|
54
|
+
@question_path = question_path
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :log
|
58
|
+
|
59
|
+
def open
|
60
|
+
# This is basically a no-op. Some other question writer implementations may need to
|
61
|
+
# open and close the question as a whole.
|
62
|
+
yield
|
63
|
+
end
|
64
|
+
|
65
|
+
# Given a path within the question such as `assets/style.css` and the data, writes
|
66
|
+
# the file. Returns true if the file was written successfully. Returns false is the
|
67
|
+
# file was unwritable.
|
68
|
+
def write(sub_path, data)
|
69
|
+
path = File.expand_path(File.join(@root, @question_path, sub_path))
|
70
|
+
@log.debug "W: #{path} (#{data.length} bytes)"
|
71
|
+
begin
|
72
|
+
FileUtils.mkdir_p File.dirname(path)
|
73
|
+
File.open(path, 'w') do |f|
|
74
|
+
f << data
|
75
|
+
end
|
76
|
+
true
|
77
|
+
rescue Exception
|
78
|
+
fail_write path
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def fail_write(path)
|
85
|
+
@log.warn "Could not write #{path}"
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module QuestionCompiler
|
2
|
+
module Server
|
3
|
+
def self.start(port)
|
4
|
+
require 'rack'
|
5
|
+
app = QuestionHandler.new Dir.getwd
|
6
|
+
begin
|
7
|
+
Rack::Handler::WEBrick.run app, Port: port
|
8
|
+
ensure
|
9
|
+
app.cleanup
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class QuestionHandler
|
14
|
+
def call(env)
|
15
|
+
req = Rack::Request.new env
|
16
|
+
|
17
|
+
if req.path == '/question.html'
|
18
|
+
serve_question
|
19
|
+
else
|
20
|
+
maybe_serve_context_file env, req
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def cleanup
|
25
|
+
path = File.join @root, '.context-path'
|
26
|
+
if File.exists? path
|
27
|
+
File.delete path
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(root)
|
32
|
+
@root = root
|
33
|
+
@context_reader = LocalContextReader.new File.join(@root, '../../../contexts')
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_context_path
|
37
|
+
path = File.join @root, '.context-path'
|
38
|
+
if File.exists? path
|
39
|
+
File.read path
|
40
|
+
else
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def maybe_serve_context_file(env, req)
|
46
|
+
if context_path = read_context_path
|
47
|
+
context_root = File.expand_path(
|
48
|
+
File.join(@root, '../../../contexts', context_path)
|
49
|
+
)
|
50
|
+
file_path = File.join(context_root, req.path)
|
51
|
+
if File.exists? file_path
|
52
|
+
Rack::File.new(context_root).call env
|
53
|
+
else
|
54
|
+
Rack::File.new(@root).call env
|
55
|
+
end
|
56
|
+
else
|
57
|
+
serve_generic env
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def serve_generic(env)
|
62
|
+
Rack::File.new(@root).call env
|
63
|
+
end
|
64
|
+
|
65
|
+
def serve_question
|
66
|
+
compiler = QuestionCompiler::Compiler.new(Logger.new(STDOUT))
|
67
|
+
question_src = File.read 'question.html'
|
68
|
+
question_doc = Nokogiri.HTML question_src
|
69
|
+
question_doc = compiler.transform_question question_doc, @context_reader
|
70
|
+
write_context_path compiler.context_path
|
71
|
+
[200, {'Content-Type' => 'text/html'}, [question_doc.to_html]]
|
72
|
+
end
|
73
|
+
|
74
|
+
def write_context_path(context_path)
|
75
|
+
File.open('.context-path', 'w') do |f|
|
76
|
+
f << context_path
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module QuestionCompiler
|
4
|
+
class Zipper
|
5
|
+
# Zips each question and each context, placing the results into the `zip` subfolder.
|
6
|
+
# Creates `zip` if it doesn't exist.
|
7
|
+
#
|
8
|
+
# Pass the path to the root folder. I.e. the folder that contains the subfolders
|
9
|
+
# `questions` and `contexts`.
|
10
|
+
def zip_all(dir)
|
11
|
+
FileUtils.mkdir_p File.join(dir, 'zip')
|
12
|
+
|
13
|
+
# Zip each question.
|
14
|
+
Dir.glob(File.join(dir, 'questions/*/*')).each do |question_path|
|
15
|
+
if File.directory? question_path
|
16
|
+
question_name = File.basename(question_path).sub('/', '-')
|
17
|
+
zip_folder(
|
18
|
+
question_path,
|
19
|
+
File.join(dir, 'zip', question_name + '.qst.zip')
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Zip each context.
|
25
|
+
Dir.glob(File.join(dir, 'contexts/*/*')).each do |context_path|
|
26
|
+
if File.directory? context_path
|
27
|
+
context_name = File.basename(context_path).sub('/', '-')
|
28
|
+
zip_folder(
|
29
|
+
context_path,
|
30
|
+
File.join(dir, 'zip', context_name + '.ctx.zip')
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Zips up `src_path` and writes the zip file to `dst_path`.
|
39
|
+
def zip_folder(src_path, dst_path)
|
40
|
+
FileUtils.rm_f dst_path
|
41
|
+
Zip::File.open(dst_path, Zip::File::CREATE) do |zip|
|
42
|
+
# Nest everything inside one folder.
|
43
|
+
zip_path_prefix = File.basename(src_path)
|
44
|
+
zip.mkdir zip_path_prefix
|
45
|
+
zip_folder_recursively(
|
46
|
+
zip_path_prefix, # Zip path prefix.
|
47
|
+
File.expand_path(src_path), # Disk path prefix.
|
48
|
+
'', # Folder disk path.
|
49
|
+
zip
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Helper method for `#zip_folder`.
|
55
|
+
#
|
56
|
+
# In this method, we document each path variable with reference to the following
|
57
|
+
# example filesystem layout, where root is the folder being zipped:
|
58
|
+
# root
|
59
|
+
# apples.txt
|
60
|
+
# dir-1
|
61
|
+
# dir-2
|
62
|
+
# bananas.txt
|
63
|
+
def zip_folder_recursively(
|
64
|
+
zip_path_prefix, # E.g. 'root'
|
65
|
+
disk_path_prefix, # E.g. '/home/username/documents/root'
|
66
|
+
folder_path, # E.g. 'dir-1/dir-2'
|
67
|
+
zip
|
68
|
+
)
|
69
|
+
# E.g. ['apples.txt', 'subfolder']
|
70
|
+
entries = Dir.entries(File.join(disk_path_prefix, folder_path)) - ['.', '..']
|
71
|
+
|
72
|
+
entries.each do |entry|
|
73
|
+
# E.g. '/home/username/documents/root/dir-1/dir-2/bananas.txt'
|
74
|
+
abs_disk_path = File.join disk_path_prefix, folder_path, entry
|
75
|
+
|
76
|
+
if File.directory? abs_disk_path
|
77
|
+
# E.g. 'root/dir-1/dir-2'
|
78
|
+
zip.mkdir File.join(zip_path_prefix, folder_path, entry)
|
79
|
+
|
80
|
+
zip_folder_recursively(
|
81
|
+
zip_path_prefix, # E.g. 'root'
|
82
|
+
disk_path_prefix, # E.g. '/home/username/documents/root'
|
83
|
+
File.join(folder_path, entry), # E.g. 'dir-1/dir-2'
|
84
|
+
zip
|
85
|
+
)
|
86
|
+
else
|
87
|
+
zip.add(
|
88
|
+
# E.g. 'root/dir-1/dir-2/bananas.txt'
|
89
|
+
File.join(zip_path_prefix, folder_path, entry),
|
90
|
+
# E.g. '/home/username/documents/root/dir-1/dir-2/bananas.txt'
|
91
|
+
File.join(disk_path_prefix, folder_path, entry)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: question_compiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jarrett Colby
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubyzip
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1'
|
69
|
+
description:
|
70
|
+
email: jarrett@uchicago.edu
|
71
|
+
executables:
|
72
|
+
- qc
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- bin/qc
|
77
|
+
- lib/question_compiler.rb
|
78
|
+
- lib/question_compiler/command.rb
|
79
|
+
- lib/question_compiler/compiler.rb
|
80
|
+
- lib/question_compiler/local.rb
|
81
|
+
- lib/question_compiler/server.rb
|
82
|
+
- lib/question_compiler/version.rb
|
83
|
+
- lib/question_compiler/zipper.rb
|
84
|
+
homepage:
|
85
|
+
licenses: []
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.2.2
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Compiler for Number Stories questions
|
107
|
+
test_files: []
|