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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/trackler/version.rb +1 -1
  3. data/tracks/go/exercises/word-count/cases_test.go +23 -4
  4. data/tracks/go/exercises/word-count/example.go +6 -2
  5. data/tracks/go/exercises/word-count/word_count.go +1 -1
  6. data/tracks/go/exercises/word-count/word_count_test.go +1 -1
  7. data/tracks/pony/.travis.yml +16 -4
  8. data/tracks/pony/bin/install-deps +12 -0
  9. data/tracks/pony/bin/test-exercises +22 -0
  10. data/tracks/ruby/Gemfile +1 -0
  11. data/tracks/ruby/Rakefile +1 -1
  12. data/tracks/ruby/bin/generate +4 -29
  13. data/tracks/ruby/exercises/all-your-base/.meta/.version +1 -0
  14. data/tracks/ruby/exercises/all-your-base/all_your_base_test.rb +50 -60
  15. data/tracks/ruby/exercises/all-your-base/example.rb +13 -8
  16. data/tracks/ruby/exercises/all-your-base/example.tt +17 -0
  17. data/tracks/ruby/lib/all_your_base_cases.rb +109 -0
  18. data/tracks/ruby/lib/generator.rb +21 -100
  19. data/tracks/ruby/lib/generator/command_line.rb +119 -0
  20. data/tracks/ruby/lib/generator/files.rb +58 -0
  21. data/tracks/ruby/lib/generator/files/metadata_files.rb +22 -0
  22. data/tracks/ruby/lib/generator/files/track_files.rb +83 -0
  23. data/tracks/ruby/lib/generator/git_command.rb +8 -0
  24. data/tracks/ruby/lib/generator/repository.rb +62 -0
  25. data/tracks/ruby/lib/generator/template_values.rb +47 -0
  26. data/tracks/ruby/lib/helper.rb +5 -2
  27. data/tracks/ruby/test/fixtures/metadata/exercises/alpha/canonical-data.json +8 -9
  28. data/tracks/ruby/test/fixtures/{exercises → xruby/exercises}/alpha/.meta/.version +0 -0
  29. data/tracks/ruby/test/fixtures/{exercises → xruby/exercises}/alpha/example.rb +0 -0
  30. data/tracks/ruby/test/fixtures/xruby/exercises/alpha/example.tt +20 -0
  31. data/tracks/ruby/test/fixtures/xruby/lib/alpha_cases.rb +20 -0
  32. data/tracks/ruby/test/fixtures/xruby/lib/beta_cases.rb +0 -0
  33. data/tracks/ruby/test/generator/command_line_test.rb +83 -0
  34. data/tracks/ruby/test/generator/files/metadata_files_test.rb +25 -0
  35. data/tracks/ruby/test/generator/files/track_files_test.rb +117 -0
  36. data/tracks/ruby/test/generator/files_test.rb +63 -0
  37. data/tracks/ruby/test/generator/git_command_test.rb +9 -0
  38. data/tracks/ruby/test/generator/repository_test.rb +98 -0
  39. data/tracks/ruby/test/generator/template_values_test.rb +59 -0
  40. data/tracks/ruby/test/generator_test.rb +19 -63
  41. data/tracks/ruby/test/test_helper.rb +8 -5
  42. metadata +26 -6
  43. data/tracks/ruby/test/fixtures/exercises/alpha/alpha_test.rb +0 -1
  44. 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
- #!/usr/bin/env ruby
2
-
3
- require 'erb'
4
- require 'json'
5
- require 'ostruct'
6
-
7
- # Wrap git shell commands.
8
- class GitCommand
9
- def self.short_sha(base_path)
10
- git_path = File.join(base_path, '.git')
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
- def increment_version
98
- File.open(version_filename, 'w') do |f|
99
- f.write version + 1
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
- def increment_version_in_example
104
- contents = File.read(path_to('example.rb'))
105
- File.open(path_to('example.rb'), 'w') do |f|
106
- f.write contents.gsub("VERSION = #{version}", "VERSION = #{version + 1}")
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