trackler 2.0.6.1 → 2.0.6.2
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 +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
|