trackler 2.0.6.1 → 2.0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/tracks/go/exercises/word-count/cases_test.go +23 -4
- data/tracks/go/exercises/word-count/example.go +6 -2
- data/tracks/go/exercises/word-count/word_count.go +1 -1
- data/tracks/go/exercises/word-count/word_count_test.go +1 -1
- data/tracks/pony/.travis.yml +16 -4
- data/tracks/pony/bin/install-deps +12 -0
- data/tracks/pony/bin/test-exercises +22 -0
- data/tracks/ruby/Gemfile +1 -0
- data/tracks/ruby/Rakefile +1 -1
- data/tracks/ruby/bin/generate +4 -29
- data/tracks/ruby/exercises/all-your-base/.meta/.version +1 -0
- data/tracks/ruby/exercises/all-your-base/all_your_base_test.rb +50 -60
- data/tracks/ruby/exercises/all-your-base/example.rb +13 -8
- data/tracks/ruby/exercises/all-your-base/example.tt +17 -0
- data/tracks/ruby/lib/all_your_base_cases.rb +109 -0
- data/tracks/ruby/lib/generator.rb +21 -100
- data/tracks/ruby/lib/generator/command_line.rb +119 -0
- data/tracks/ruby/lib/generator/files.rb +58 -0
- data/tracks/ruby/lib/generator/files/metadata_files.rb +22 -0
- data/tracks/ruby/lib/generator/files/track_files.rb +83 -0
- data/tracks/ruby/lib/generator/git_command.rb +8 -0
- data/tracks/ruby/lib/generator/repository.rb +62 -0
- data/tracks/ruby/lib/generator/template_values.rb +47 -0
- data/tracks/ruby/lib/helper.rb +5 -2
- data/tracks/ruby/test/fixtures/metadata/exercises/alpha/canonical-data.json +8 -9
- data/tracks/ruby/test/fixtures/{exercises → xruby/exercises}/alpha/.meta/.version +0 -0
- data/tracks/ruby/test/fixtures/{exercises → xruby/exercises}/alpha/example.rb +0 -0
- data/tracks/ruby/test/fixtures/xruby/exercises/alpha/example.tt +20 -0
- data/tracks/ruby/test/fixtures/xruby/lib/alpha_cases.rb +20 -0
- data/tracks/ruby/test/fixtures/xruby/lib/beta_cases.rb +0 -0
- data/tracks/ruby/test/generator/command_line_test.rb +83 -0
- data/tracks/ruby/test/generator/files/metadata_files_test.rb +25 -0
- data/tracks/ruby/test/generator/files/track_files_test.rb +117 -0
- data/tracks/ruby/test/generator/files_test.rb +63 -0
- data/tracks/ruby/test/generator/git_command_test.rb +9 -0
- data/tracks/ruby/test/generator/repository_test.rb +98 -0
- data/tracks/ruby/test/generator/template_values_test.rb +59 -0
- data/tracks/ruby/test/generator_test.rb +19 -63
- data/tracks/ruby/test/test_helper.rb +8 -5
- metadata +26 -6
- data/tracks/ruby/test/fixtures/exercises/alpha/alpha_test.rb +0 -1
- data/tracks/ruby/test/fixtures/exercises/alpha/example.tt +0 -1
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative 'all_your_base'
|
3
|
+
|
4
|
+
# Test data version: <%= sha1 %>
|
5
|
+
class AllYourBaseTest < Minitest::Test<% test_cases.each do |test_case| %>
|
6
|
+
def <%= test_case.test_name %>
|
7
|
+
<%= test_case.skipped %>
|
8
|
+
<%= test_case.workload %>
|
9
|
+
end
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<%= IO.read(XRUBY_LIB + '/bookkeeping.md') %>
|
13
|
+
def test_bookkeeping
|
14
|
+
skip
|
15
|
+
assert_equal <%= version.next %>, BookKeeping::VERSION
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class AllYourBaseCase < OpenStruct
|
2
|
+
def test_name
|
3
|
+
'test_%s' % description.downcase.tr(' -', '_')
|
4
|
+
end
|
5
|
+
|
6
|
+
def workload
|
7
|
+
indent(4, (assignments + assertion).join("\n")) + "\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
def skipped
|
11
|
+
index.zero? ? '# skip' : 'skip'
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def indent(size, text)
|
17
|
+
text.lines.each_with_object('') do |line, obj|
|
18
|
+
obj << (line == "\n" ? line : ' ' * size + line)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def assignments
|
23
|
+
[
|
24
|
+
"digits = #{input_digits}",
|
25
|
+
"input_base = #{input_base}",
|
26
|
+
"output_base = #{output_base}",
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
def assertion
|
31
|
+
return error_assertion unless expected
|
32
|
+
|
33
|
+
[
|
34
|
+
"expected = #{expected}",
|
35
|
+
"",
|
36
|
+
"converted = BaseConverter.convert(input_base, digits, output_base)",
|
37
|
+
"",
|
38
|
+
"assert_equal expected, converted,",
|
39
|
+
indent(13, error_message),
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def error_assertion
|
44
|
+
[
|
45
|
+
"",
|
46
|
+
"assert_raises ArgumentError do",
|
47
|
+
" BaseConverter.convert(input_base, digits, output_base)",
|
48
|
+
"end",
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
def error_message
|
53
|
+
%q("Input base: #{input_base}, output base #{output_base}. " \\) \
|
54
|
+
"\n" + %q("Expected #{expected} but got #{converted}.")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class AllYourBaseCase::PreProcessor
|
59
|
+
class << self
|
60
|
+
attr_reader :row
|
61
|
+
|
62
|
+
def call(row)
|
63
|
+
@row = row
|
64
|
+
|
65
|
+
row.merge('expected' => expected_value)
|
66
|
+
end
|
67
|
+
|
68
|
+
private :row
|
69
|
+
private
|
70
|
+
|
71
|
+
def expected_value
|
72
|
+
return row['expected'] if row['expected']
|
73
|
+
|
74
|
+
if invalid_input_digits? || invalid_bases?
|
75
|
+
nil
|
76
|
+
elsif row['input_digits'].empty?
|
77
|
+
[]
|
78
|
+
elsif input_of_zero?
|
79
|
+
[0]
|
80
|
+
else
|
81
|
+
handle_special_cases
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def invalid_input_digits?
|
86
|
+
row['input_digits'].any? { |x| x < 0 || x >= row['input_base'] }
|
87
|
+
end
|
88
|
+
|
89
|
+
def invalid_bases?
|
90
|
+
row['input_base'] <= 1 || row['output_base'] <= 1
|
91
|
+
end
|
92
|
+
|
93
|
+
def input_of_zero?
|
94
|
+
row['input_digits'].all? { |x| x == 0 }
|
95
|
+
end
|
96
|
+
|
97
|
+
def handle_special_cases
|
98
|
+
[4,2] if row['input_digits'] == [0, 6, 0]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
AllYourBaseCases = proc do |data|
|
104
|
+
JSON.parse(data)['cases'].map.with_index do |row, i|
|
105
|
+
AllYourBaseCase.new(
|
106
|
+
AllYourBaseCase::PreProcessor.call(row).merge(index: i),
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
@@ -1,109 +1,30 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
`git --git-dir=#{git_path} log -1 --pretty=format:'%h'`
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class Generator
|
16
|
-
METADATA_REPOSITORY = 'x-common'.freeze
|
17
|
-
|
18
|
-
attr_reader :name, :cases
|
19
|
-
def initialize(name, cases, metadata_repository_path = nil, xruby_root = nil)
|
20
|
-
@name = name
|
21
|
-
@cases = cases
|
22
|
-
@metadata_repository_path = metadata_repository_path || default_metadata_path
|
23
|
-
@xruby_root = xruby_root || '.'
|
24
|
-
end
|
25
|
-
|
26
|
-
def default_metadata_path
|
27
|
-
File.join('..', METADATA_REPOSITORY)
|
28
|
-
end
|
29
|
-
|
30
|
-
def metadata_dir
|
31
|
-
File.join(@metadata_repository_path, 'exercises', name)
|
32
|
-
end
|
33
|
-
|
34
|
-
def exercise_dir
|
35
|
-
File.join(@xruby_root, 'exercises', name)
|
36
|
-
end
|
37
|
-
|
38
|
-
def exercise_meta_dir
|
39
|
-
File.join(exercise_dir, '.meta')
|
40
|
-
end
|
41
|
-
|
42
|
-
def version_filename
|
43
|
-
File.join(exercise_meta_dir, '.version')
|
44
|
-
end
|
45
|
-
|
46
|
-
def data
|
47
|
-
File.read(File.join(metadata_dir, 'canonical-data.json'))
|
48
|
-
end
|
49
|
-
|
50
|
-
def path_to(file)
|
51
|
-
File.join(exercise_dir, file)
|
52
|
-
end
|
53
|
-
|
54
|
-
def version
|
55
|
-
@version ||= File.read(version_filename).strip.to_i
|
56
|
-
end
|
57
|
-
|
58
|
-
def sha1
|
59
|
-
GitCommand.short_sha(@metadata_repository_path)
|
60
|
-
end
|
61
|
-
|
62
|
-
def test_cases
|
63
|
-
cases.call(data)
|
64
|
-
end
|
65
|
-
|
66
|
-
def metadata_repository_missing_message
|
67
|
-
<<-EOM.gsub(/^ {6}/, '')
|
68
|
-
|
69
|
-
'#{METADATA_REPOSITORY}' repository not found.
|
70
|
-
Try running the command:
|
71
|
-
git clone https://github.com/exercism/#{METADATA_REPOSITORY}.git "#{metadata_dir}"
|
72
|
-
|
73
|
-
EOM
|
74
|
-
end
|
75
|
-
|
76
|
-
def generate
|
77
|
-
check_metadata_repository_exists
|
78
|
-
generate_test_file
|
79
|
-
increment_version
|
80
|
-
increment_version_in_example
|
81
|
-
end
|
82
|
-
|
83
|
-
def check_metadata_repository_exists
|
84
|
-
unless File.directory?(metadata_dir)
|
85
|
-
$stderr.puts metadata_repository_missing_message
|
86
|
-
fail Errno::ENOENT.new(metadata_dir)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def generate_test_file
|
91
|
-
File.open(path_to("#{name.gsub(/[ -]/, '_')}_test.rb"), 'w') do |f|
|
92
|
-
template = File.read(path_to('example.tt'))
|
93
|
-
f.write ERB.new(template, nil, '<>').result binding
|
1
|
+
require 'require_all'
|
2
|
+
require_rel 'generator' # Include everything in the 'generator' subdirectory
|
3
|
+
|
4
|
+
module Generator
|
5
|
+
# Immutable value object for storing paths
|
6
|
+
class Paths
|
7
|
+
attr_reader :track, :metadata
|
8
|
+
def initialize(track:, metadata:)
|
9
|
+
@track = track
|
10
|
+
@metadata = metadata
|
94
11
|
end
|
95
12
|
end
|
96
13
|
|
97
|
-
|
98
|
-
|
99
|
-
|
14
|
+
# This contains the order for updating/generating the files. (Strategy pattern).
|
15
|
+
# Doesn't update the version information.
|
16
|
+
class GenerateTests < RepositoryDelegator
|
17
|
+
def call
|
18
|
+
create_tests_file
|
100
19
|
end
|
101
20
|
end
|
102
21
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
22
|
+
# Update everything.
|
23
|
+
class UpdateVersionAndGenerateTests < RepositoryDelegator
|
24
|
+
def call
|
25
|
+
update_tests_version
|
26
|
+
update_example_solution
|
27
|
+
create_tests_file
|
107
28
|
end
|
108
29
|
end
|
109
30
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Generator
|
5
|
+
# Processes the command line arguments and sets everthing up and returns a
|
6
|
+
# generator that can be called
|
7
|
+
class CommandLine
|
8
|
+
def initialize(paths)
|
9
|
+
@paths = paths
|
10
|
+
@options = default_options
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(args)
|
14
|
+
parse_options(args)
|
15
|
+
generator if valid?
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
validate_paths && validate_options && validate_exercise && validate_cases
|
22
|
+
end
|
23
|
+
|
24
|
+
def generator
|
25
|
+
generator_class.new(repository)
|
26
|
+
end
|
27
|
+
|
28
|
+
def generator_class
|
29
|
+
@options[:freeze] ? GenerateTests : UpdateVersionAndGenerateTests
|
30
|
+
end
|
31
|
+
|
32
|
+
def repository
|
33
|
+
if @options[:verbose]
|
34
|
+
LoggingRepository.new(paths: @paths, exercise_name: @options[:exercise_name], logger: logger)
|
35
|
+
else
|
36
|
+
Repository.new(paths: @paths, exercise_name: @options[:exercise_name])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_options(args)
|
41
|
+
args = args.dup
|
42
|
+
option_parser.parse!(args)
|
43
|
+
@options[:exercise_name] = args.shift
|
44
|
+
end
|
45
|
+
|
46
|
+
def logger
|
47
|
+
logger = Logger.new($stdout)
|
48
|
+
logger.formatter = proc { |_severity, _datetime, _progname, msg| "#{msg}\n" }
|
49
|
+
logger.level = @options[:verbose] ? Logger::DEBUG : Logger::ERROR
|
50
|
+
logger
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_options
|
54
|
+
{
|
55
|
+
freeze: false,
|
56
|
+
verbose: false,
|
57
|
+
exercise_name: nil
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_options
|
62
|
+
return true unless @options[:help]
|
63
|
+
$stdout.puts usage
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_paths
|
68
|
+
validate_metadata_repository
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_exercise
|
72
|
+
return true if @options[:exercise_name]
|
73
|
+
$stderr.puts "Exercise name required!\n"
|
74
|
+
$stdout.puts usage
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_cases
|
79
|
+
return true if available_generators.include?(@options[:exercise_name])
|
80
|
+
$stderr.puts "A generator does not currently exist for #{@options[:exercise_name]}!"
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
def available_generators
|
85
|
+
Files::GeneratorCases.available(@paths.track)
|
86
|
+
end
|
87
|
+
|
88
|
+
def option_parser
|
89
|
+
@option_parser ||= OptionParser.new do |parser|
|
90
|
+
parser.banner = "Usage: #{$PROGRAM_NAME} [options] exercise-generator"
|
91
|
+
parser.on('-f', '--freeze', 'Don\'t update test version') { |value| @options[:freeze] = value }
|
92
|
+
parser.on('-h', '--help', 'Displays this help message') { |value| @options[:help] = value }
|
93
|
+
parser.on('-v', '--verbose', 'Display progress messages') { |value| @options[:verbose] = value }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def usage
|
98
|
+
option_parser.help <<
|
99
|
+
"\nAvailable exercise generators:\n" <<
|
100
|
+
available_generators.sort.join(' ')
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_metadata_repository
|
104
|
+
return true if File.directory?(@paths.metadata)
|
105
|
+
$stderr.puts metadata_repository_missing_message(@paths.metadata)
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
def metadata_repository_missing_message(repository)
|
110
|
+
<<-EOM.gsub(/^ {6}/, '')
|
111
|
+
|
112
|
+
'x-common' repository not found.
|
113
|
+
Try running the command:
|
114
|
+
git clone https://github.com/exercism/x-common.git "#{repository}"
|
115
|
+
|
116
|
+
EOM
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Generator
|
4
|
+
module Files
|
5
|
+
class Readable
|
6
|
+
attr_reader :filename, :repository_root
|
7
|
+
def initialize(filename:,repository_root: nil)
|
8
|
+
@filename = filename
|
9
|
+
@repository_root = repository_root
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
File.read(filename) if File.exist?(filename)
|
14
|
+
end
|
15
|
+
|
16
|
+
def abbreviated_commit_hash
|
17
|
+
GitCommand.abbreviated_commit_hash(git_path, relative_filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def relative_filename
|
23
|
+
Pathname.new(filename).relative_path_from(Pathname.new(repository_root)).to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def git_path
|
27
|
+
File.join(repository_root, '.git')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Writable < Readable
|
32
|
+
def save(content)
|
33
|
+
write(content) unless content == to_s
|
34
|
+
content
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def write(content)
|
40
|
+
File.open(filename, 'w') do |file|
|
41
|
+
file.write content
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# An Exercise is used as part of a Repository
|
47
|
+
# so expects :paths and :exercise_name to be defined.
|
48
|
+
module Exercise
|
49
|
+
def paths
|
50
|
+
fail NotImplementedError, 'Should return a Generator::Paths object'
|
51
|
+
end
|
52
|
+
|
53
|
+
def exercise_name
|
54
|
+
fail NotImplementedError, 'Should return a String object'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Generator
|
2
|
+
module Files
|
3
|
+
module MetadataFiles
|
4
|
+
include Exercise
|
5
|
+
|
6
|
+
def canonical_data
|
7
|
+
CanonicalDataFile.new(
|
8
|
+
filename: File.join(exercise_metadata_path, 'canonical-data.json'),
|
9
|
+
repository_root: paths.metadata)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def exercise_metadata_path
|
15
|
+
File.join(paths.metadata, 'exercises', exercise_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class CanonicalDataFile < Readable
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|