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.
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