From 77a991b067713a6e3ded9d4a367237b40de2cef0 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 9 Nov 2024 14:21:57 +0100 Subject: [PATCH 01/22] Impliment basic prototype as proof of concept --- bin/generate.rb | 35 ++++++ .../practice/acronym/.meta/test_template.erb | 13 +++ exercises/practice/acronym/acronym_test.rb | 16 +-- generatorv2/lib/generator.rb | 103 ++++++++++++++++++ generatorv2/test/misc/tests.toml | 31 ++++++ generatorv2/test/misc/tests_all_excluded.toml | 38 +++++++ generatorv2/test/misc/tests_no_include.toml | 32 ++++++ generatorv2/test/toml_test.rb | 18 +++ 8 files changed, 278 insertions(+), 8 deletions(-) create mode 100644 bin/generate.rb create mode 100644 exercises/practice/acronym/.meta/test_template.erb create mode 100644 generatorv2/lib/generator.rb create mode 100644 generatorv2/test/misc/tests.toml create mode 100644 generatorv2/test/misc/tests_all_excluded.toml create mode 100644 generatorv2/test/misc/tests_no_include.toml create mode 100644 generatorv2/test/toml_test.rb diff --git a/bin/generate.rb b/bin/generate.rb new file mode 100644 index 0000000000..6beb27e0ad --- /dev/null +++ b/bin/generate.rb @@ -0,0 +1,35 @@ +require 'optparse' +require_relative '../generatorv2/lib/generator' + +parser = OptionParser.new + +parser.on('-v', '--version', 'Print the version') do + puts '0.1.0' +end + +parser.on('-h', '--help', 'Prints help') do + puts parser +end + +parser.on('-a', '--all', 'Generate all exercises') do + exercises = Dir.entries('./exercises/practice').select { |f| File.directory? File.join('./exercises/practice', f) } + exercises.each do |exercise| + if File.exist?("./exercises/practice/#{exercise}/.meta/test_template.erb") + Generator.new(exercise).generate + end + end +end + +parser.on('--verify', 'Verify all exercises') do + exercises = Dir.entries('./exercises/practice').select { |f| File.directory? File.join('./exercises/practice', f) } + exercises.each do |exercise| + puts "Verifying #{exercise}" + system("ruby ./exercises/practice/#{exercise}/#{exercise}__test.rb") + end +end + +parser.on('-e', '--exercise EXERCISE', 'The exercise to generate') do |exercise| + Generator.new(exercise).generate +end + +parser.parse! diff --git a/exercises/practice/acronym/.meta/test_template.erb b/exercises/practice/acronym/.meta/test_template.erb new file mode 100644 index 0000000000..22d3c07863 --- /dev/null +++ b/exercises/practice/acronym/.meta/test_template.erb @@ -0,0 +1,13 @@ +require 'minitest/autorun' +require_relative 'acronym' + +class AcronymTest < Minitest::Test +<% json["cases"].each do |cases| %> + def test_<%= underscore(cases["description"]) %> + <%= status() %> + assert_equal '<%= cases["expected"] %>', <%= camel_case(json["exercise"]) %>.<%= underscore(cases["property"]) %>('<%= cases["input"]["phrase"] %>') + end +<% end %> +end + + diff --git a/exercises/practice/acronym/acronym_test.rb b/exercises/practice/acronym/acronym_test.rb index 8db62dcfb9..15c65a69b6 100644 --- a/exercises/practice/acronym/acronym_test.rb +++ b/exercises/practice/acronym/acronym_test.rb @@ -4,37 +4,37 @@ class AcronymTest < Minitest::Test def test_basic # skip - assert_equal "PNG", Acronym.abbreviate('Portable Network Graphics') + assert_equal 'PNG', Acronym.abbreviate('Portable Network Graphics') end def test_lowercase_words skip - assert_equal "ROR", Acronym.abbreviate('Ruby on Rails') + assert_equal 'ROR', Acronym.abbreviate('Ruby on Rails') end def test_punctuation skip - assert_equal "FIFO", Acronym.abbreviate('First In, First Out') + assert_equal 'FIFO', Acronym.abbreviate('First In, First Out') end def test_all_caps_word skip - assert_equal "GIMP", Acronym.abbreviate('GNU Image Manipulation Program') + assert_equal 'GIMP', Acronym.abbreviate('GNU Image Manipulation Program') end def test_punctuation_without_whitespace skip - assert_equal "CMOS", Acronym.abbreviate('Complementary metal-oxide semiconductor') + assert_equal 'CMOS', Acronym.abbreviate('Complementary metal-oxide semiconductor') end def test_very_long_abbreviation skip - assert_equal "ROTFLSHTMDCOALM", - Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') + assert_equal 'ROTFLSHTMDCOALM', + Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') end def test_consecutive_delimiters skip - assert_equal "SIMUFTA", Acronym.abbreviate('Something - I made up from thin air') + assert_equal 'SIMUFTA', Acronym.abbreviate('Something - I made up from thin air') end end diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb new file mode 100644 index 0000000000..0dc2e59854 --- /dev/null +++ b/generatorv2/lib/generator.rb @@ -0,0 +1,103 @@ +require 'toml-rb' +require 'net/http' +require 'uri' +require 'json' +require 'erb' +require 'rubocop' + +class Generator + def initialize(exercise = nil) + @first = true + @exercise = exercise + end + + def generate + json = get_remote_files + uuid = toml("./exercises/practice/#{@exercise}/.meta/tests.toml") + additional_json(json) + json["cases"] = remove_tests(uuid, json) + status = proc { status } + camel_case = proc { |str| camel_case(str) } + underscore = proc { |str| underscore(str) } + template = ERB.new File.read("./exercises/practice/#{@exercise}/.meta/test_template.erb") + + result = template.result(binding) + + File.write("./exercises/practice/#{@exercise}/#{@exercise}_test.rb", result) + cli = RuboCop::CLI.new + cli.run(['-x', "--force-default-config", "exercises/practice/#{@exercise}/#{@exercise}_test.rb"]) + end + + def underscore(str) + str.each_char.reduce("") do |acc, x| + acc << if x == ' ' + '_' + else + x.downcase + end + acc + end + end + + def camel_case(str) + str.split('_').map(&:capitalize).join + end + + def status + if @first + @first = false + return "# skip" + end + "skip" + end + + def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml") + raise "Toml not found: #{path}" unless File.exist?(path) + + uuid = TomlRB.load_file(path) + uuid.filter do |_k, v| + !v.any? { |k, v| k == "include" && !v } + end.map { |k, _v| k } + end + + def get_remote_files + url = URI.parse("https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/#{@exercise}/canonical-data.json") + response = Net::HTTP.get_response(url) + case response + when Net::HTTPSuccess + JSON.parse(response.body) + when Net::HTTPNotFound + check_for_local_canonical_data + else + raise "Error while requesting the #{@exercise} data file from GitHub... " + + "Status was #{response.code}" + end + end + + def check_for_local_canonical_data(path = "./exercises/practice/#{@exercise}/canonical-data.json") + raise "No canonical-data.json found in #{@exercise} directory" unless File.exist?(path) + + JSON.parse(File.read(path)) + end + + def additional_json(json) + file_path = "./exercises/practice/#{@exercise}/.meta/additional_tests.json" + return unless File.exist?(file_path) + + JSON.parse(File.read(file_path))["cases"].each do |test| + json["cases"] << test + end + end + + def remove_tests(uuid, json) + json["cases"].each_with_object([]) do |x, acc| + if x["cases"] + acc << remove_tests(uuid, json) + elsif uuid.include?(x["uuid"]) + acc << x + end + end + end +end + +# Generator.new("acronym").get_remote_files diff --git a/generatorv2/test/misc/tests.toml b/generatorv2/test/misc/tests.toml new file mode 100644 index 0000000000..5c5b9fd84e --- /dev/null +++ b/generatorv2/test/misc/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] +description = "basic" + +[79ae3889-a5c0-4b01-baf0-232d31180c08] +description = "lowercase words" + +[ec7000a7-3931-4a17-890e-33ca2073a548] +description = "punctuation" + +[32dd261c-0c92-469a-9c5c-b192e94a63b0] +description = "all caps word" + +[ae2ac9fa-a606-4d05-8244-3bcc4659c1d4] +description = "punctuation without whitespace" + +[0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9] +description = "very long abbreviation" + +[6a078f49-c68d-4b7b-89af-33a1a98c28cc] +description = "consecutive delimiters" diff --git a/generatorv2/test/misc/tests_all_excluded.toml b/generatorv2/test/misc/tests_all_excluded.toml new file mode 100644 index 0000000000..a321ebeb17 --- /dev/null +++ b/generatorv2/test/misc/tests_all_excluded.toml @@ -0,0 +1,38 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] +description = "basic" +include = false + +[79ae3889-a5c0-4b01-baf0-232d31180c08] +description = "lowercase words" +include = false + +[ec7000a7-3931-4a17-890e-33ca2073a548] +description = "punctuation" +include = false + +[32dd261c-0c92-469a-9c5c-b192e94a63b0] +description = "all caps word" +include = false + +[ae2ac9fa-a606-4d05-8244-3bcc4659c1d4] +description = "punctuation without whitespace" +include = false + +[0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9] +description = "very long abbreviation" +include = false + +[6a078f49-c68d-4b7b-89af-33a1a98c28cc] +description = "consecutive delimiters" +include = false diff --git a/generatorv2/test/misc/tests_no_include.toml b/generatorv2/test/misc/tests_no_include.toml new file mode 100644 index 0000000000..97497982ec --- /dev/null +++ b/generatorv2/test/misc/tests_no_include.toml @@ -0,0 +1,32 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] +description = "basic" + +[79ae3889-a5c0-4b01-baf0-232d31180c08] +description = "lowercase words" + +[ec7000a7-3931-4a17-890e-33ca2073a548] +description = "punctuation" + +[32dd261c-0c92-469a-9c5c-b192e94a63b0] +description = "all caps word" + +[ae2ac9fa-a606-4d05-8244-3bcc4659c1d4] +description = "punctuation without whitespace" + +[0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9] +description = "very long abbreviation" +include = false + +[6a078f49-c68d-4b7b-89af-33a1a98c28cc] +description = "consecutive delimiters" diff --git a/generatorv2/test/toml_test.rb b/generatorv2/test/toml_test.rb new file mode 100644 index 0000000000..7f3c69c458 --- /dev/null +++ b/generatorv2/test/toml_test.rb @@ -0,0 +1,18 @@ +require_relative '../lib/generator' +require 'minitest/autorun' + +class GeneratorTest < Minitest::Test + def test_importning_toml + assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9 6a078f49-c68d-4b7b-89af-33a1a98c28cc], + Generator.new("two_fer").toml("./test/misc/tests.toml") + end + + def test_importing_toml_with_no_include + assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 6a078f49-c68d-4b7b-89af-33a1a98c28cc], + Generator.new("two_fer").toml("./test/misc/tests_no_include.toml") + end + + def test_importing_toml_with_all_excluded + assert_empty Generator.new("two_fer").toml("./test/misc/tests_all_excluded.toml") + end +end From 039911c6d8d79f24c1ee040363fc468b13368f75 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 9 Nov 2024 17:48:51 +0100 Subject: [PATCH 02/22] Add ci and more tests --- .github/workflows/generator-tests.yml | 33 +++++ Gemfile | 1 + Rakefile | 5 + bin/generate.rb | 10 +- exercises/practice/acronym/acronym_test.rb | 2 +- generatorv2/README.md | 139 +++++++++++++++++++++ generatorv2/lib/generator.rb | 14 +-- generatorv2/test/toml_test.rb | 6 +- generatorv2/test/utils_test.rb | 36 ++++++ 9 files changed, 232 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/generator-tests.yml create mode 100644 generatorv2/README.md create mode 100644 generatorv2/test/utils_test.rb diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml new file mode 100644 index 0000000000..e377705844 --- /dev/null +++ b/.github/workflows/generator-tests.yml @@ -0,0 +1,33 @@ +name: GeneratorTests +on: + pull_request: + push: + branches: + - main + schedule: + # Weekly. + - cron: '0 0 * * 0' + +jobs: + test-generator-templates: + name: Test Generator + runs-on: ubuntu-22.04 + steps: + - name: Set up Ruby + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 + with: + ruby-version: "3.3" + bundler-cache: true + run: ruby ./bin/generator.rb --verify + test-generator: + name: Check Generator Templates + runs-on: ubuntu-22.04 + container: + image: crystallang/crystal + steps: + - name: Set up Ruby + uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 + with: + ruby-version: "3.3" + bundler-cache: true + run: rake test:generator \ No newline at end of file diff --git a/Gemfile b/Gemfile index 6ec775621a..05b472f547 100644 --- a/Gemfile +++ b/Gemfile @@ -8,3 +8,4 @@ gem 'rubocop-minitest', require: false gem 'rubocop-rake', require: false gem 'simplecov', require: false gem 'racc', require: false +gem 'toml-rb', require: false diff --git a/Rakefile b/Rakefile index 0c18496369..fe00a092b5 100644 --- a/Rakefile +++ b/Rakefile @@ -25,5 +25,10 @@ namespace :test do task.pattern = 'test/**/*_test.rb' end + Rake::TestTask.new :generator do |task| + task.options = flags + task.pattern = 'generatorv2/test/**/*_test.rb' + end + ExerciseTestTasks.new options: flags end diff --git a/bin/generate.rb b/bin/generate.rb index 6beb27e0ad..824ec1e8b1 100644 --- a/bin/generate.rb +++ b/bin/generate.rb @@ -1,4 +1,5 @@ require 'optparse' +require 'tempfile' require_relative '../generatorv2/lib/generator' parser = OptionParser.new @@ -23,8 +24,13 @@ parser.on('--verify', 'Verify all exercises') do exercises = Dir.entries('./exercises/practice').select { |f| File.directory? File.join('./exercises/practice', f) } exercises.each do |exercise| - puts "Verifying #{exercise}" - system("ruby ./exercises/practice/#{exercise}/#{exercise}__test.rb") + if File.exist?("./exercises/practice/#{exercise}/.meta/test_template.erb") + current_code = File.read("./exercises/practice/#{exercise}/#{exercise}_test.rb") + f = Tempfile.create + Generator.new(exercise).generate(f.path) + generated_code = f.read + raise RuntimeError.new("The result generated for: #{exercise}, doesnt match the current file") if current_code != generated_code + end end end diff --git a/exercises/practice/acronym/acronym_test.rb b/exercises/practice/acronym/acronym_test.rb index 15c65a69b6..2acf62c9bb 100644 --- a/exercises/practice/acronym/acronym_test.rb +++ b/exercises/practice/acronym/acronym_test.rb @@ -30,7 +30,7 @@ def test_punctuation_without_whitespace def test_very_long_abbreviation skip assert_equal 'ROTFLSHTMDCOALM', - Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') + Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') end def test_consecutive_delimiters diff --git a/generatorv2/README.md b/generatorv2/README.md new file mode 100644 index 0000000000..4707dc43ef --- /dev/null +++ b/generatorv2/README.md @@ -0,0 +1,139 @@ +# Generator + +Last Updated: 2024/11/9 + +The generator is a powerful tool that can be used to generate tests for exercises based on the canonical data. +The generator is written in Ruby and is located in the `bin` directory. + +## How to use the generator + +### Things to do before running the generator + +Before running the generator you have to make sure a couple of files are in place. + +1. `tests.toml` file + +It is located under the `.meta` folder for each exercise. +The toml file is used to configure which exercises are generated and which are not. +Since the generator grabs all the data from the canonical data, so does this enable new tests that won't automatically be merged in. +Instead so does new tests have to be added to the toml file before they show up in the test file. + +If there is a test that isn't needed or something that doesn't fit Ruby you can remove it from the toml file. +By writing after the test name `include = false` and it will be skipped when generating the test file. + +2. `config.json` file, located in the root of the track + +The generator makes sure that the exercise is in the config.json so you need to add it there before running the generator. + +3. `spec` directory + +The generator will create a spec file for each exercise, so you need to make sure that the spec directory is in place. +Although there don't have to be any files in the directory, since the script will create one for you. +If it is a file already so will the generator overwrite it. + +#### Things to note + +The script which grabs info from the toml file is quite sensitive, writing the toml file in an incorrect way can brick the generator. + +Here are some examples of how you should **NOT** work with the toml file. + +Make sure that the uuid is the only thing inside of `[uuid]`, if there is, for example, an extra space so would that break it. +Here is an example + +```toml +# This would break it since it is an extra space between uuid and `]` +[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 ] +# This would break it since it is an extra space between uuid and `[` +[ 1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] +``` + +The script won't care if you write `include = true` since if it sees the uuid it will always take it as long as `include = false` is not written. +The script will not work if anything is misspelled, although the part which gets `include = false` doesn't care if it gets an extra space or not. + +**NOTE:** +You are also **NOT** allowed to write `include = false` more than once after each uuid. +Since that can lead to errors in the generator. + +Bad way: + +```toml +[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] +description = "basic" +include = false +include = false +``` + +Good way: + +```toml +[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] +description = "basic" +include = false +``` + +### Template + +The generator uses a template file to generate the test file. +The template is located under the `.meta` for each exercise. + +This template has to be manually written for each exercise. +The goal although is to make it so that you only have to write the template once and then it will be able to be used to generate new tests. + +The template file is written in [Embedded Ruby(ERB)][erb]. +ERB enables you to write Ruby code inside of the template file. +It also means that the templates can be highly customizable since you can write any Ruby code you want. + +When writing the template file it is recommended to look at already existing template files to get a better understanding of how it works. +The template is getting a slightly modified version of the canonical data, so you can check out the [canonical data][canonical data] to see the data structure. +The modification is that the cases which are not included in the toml file will be removed from the data structure. + +When writing the template so is it a special tool that can help with giving `# skip` and `skip` tags for tests. +You simply have to call the `status` method. +It will return either `# skip` or `skip` depending on if it is the first test case or not. + +Here is an example: + +``` +<%= status()%> +<%= status()%> +<%= status()%> +``` + +result: + +``` +# skip +skip +skip +``` + +### The Test Generator + +If all the earlier steps are done so can you run the generator. +To run the generator you need to have a working Ruby installation and installed all gems in the Gemfile. +The generator is located in the `bin` directory and is called `generator.rb`. + +To run the generator so do you have to be in the root directory and run the following command: + +```shell +ruby ./bin/generator.rb -e +``` + +Where `` is the same name as the slug name which is located in the `config.json` file. + +For more commands so can you run the following command: + +```shell +ruby ./bin/generator.rb --help +``` + +### Errors and warnings + +The generator will give you errors and warnings if something is wrong. +That includes if the exercise is not in the `config.json` file, if the exercise is not in the toml file, or if the template file is missing. +Also if it has a problem getting the `canonical-data.json` file so will it give you an error. +The generator also uses a formatter which will give you errors if the generated file is not formatted correctly. +The file will still be generated even if formatter gives errors, therefore can you check the file and see what is wrong and fix it in the template. + +[erb]: https://docs.ruby-lang.org/en/master/ERB.html +[canonical data]: https://github.com/exercism/problem-specifications diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb index 0dc2e59854..4f3fb15f2d 100644 --- a/generatorv2/lib/generator.rb +++ b/generatorv2/lib/generator.rb @@ -11,7 +11,7 @@ def initialize(exercise = nil) @exercise = exercise end - def generate + def generate(result_path = "./exercises/practice/#{@exercise}/#{@exercise}_test.rb") json = get_remote_files uuid = toml("./exercises/practice/#{@exercise}/.meta/tests.toml") additional_json(json) @@ -23,14 +23,14 @@ def generate result = template.result(binding) - File.write("./exercises/practice/#{@exercise}/#{@exercise}_test.rb", result) + File.write(result_path, result) cli = RuboCop::CLI.new - cli.run(['-x', "--force-default-config", "exercises/practice/#{@exercise}/#{@exercise}_test.rb"]) + cli.run(['-x', "--force-default-config", "-o", "/dev/null", result_path]) end def underscore(str) str.each_char.reduce("") do |acc, x| - acc << if x == ' ' + acc << if [' ', '-'].include?(x) '_' else x.downcase @@ -40,7 +40,7 @@ def underscore(str) end def camel_case(str) - str.split('_').map(&:capitalize).join + str.split(/[-_]/).map(&:capitalize).join end def status @@ -56,7 +56,7 @@ def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml") uuid = TomlRB.load_file(path) uuid.filter do |_k, v| - !v.any? { |k, v| k == "include" && !v } + v.none? { |k, v| k == "include" && !v } end.map { |k, _v| k } end @@ -99,5 +99,3 @@ def remove_tests(uuid, json) end end end - -# Generator.new("acronym").get_remote_files diff --git a/generatorv2/test/toml_test.rb b/generatorv2/test/toml_test.rb index 7f3c69c458..8eff369c08 100644 --- a/generatorv2/test/toml_test.rb +++ b/generatorv2/test/toml_test.rb @@ -4,15 +4,15 @@ class GeneratorTest < Minitest::Test def test_importning_toml assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9 6a078f49-c68d-4b7b-89af-33a1a98c28cc], - Generator.new("two_fer").toml("./test/misc/tests.toml") + Generator.new("two_fer").toml("generatorv2/test/misc/tests.toml") end def test_importing_toml_with_no_include assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 6a078f49-c68d-4b7b-89af-33a1a98c28cc], - Generator.new("two_fer").toml("./test/misc/tests_no_include.toml") + Generator.new("two_fer").toml("generatorv2/test/misc/tests_no_include.toml") end def test_importing_toml_with_all_excluded - assert_empty Generator.new("two_fer").toml("./test/misc/tests_all_excluded.toml") + assert_empty Generator.new("two_fer").toml("generatorv2/test/misc/tests_all_excluded.toml") end end diff --git a/generatorv2/test/utils_test.rb b/generatorv2/test/utils_test.rb new file mode 100644 index 0000000000..7590322f42 --- /dev/null +++ b/generatorv2/test/utils_test.rb @@ -0,0 +1,36 @@ +require_relative '../lib/generator' +require 'minitest/autorun' + +class UtilTest < Minitest::Test + def test_camelize + assert_equal "Acronym", + Generator.new("acronym").camel_case("acronym") + end + + def test_camelize_with_two_words + assert_equal "TwoFer", + Generator.new("two-fer").camel_case("two-fer") + end + + def test_underscore + assert_equal "acronym", + Generator.new("acronym").underscore("acronym") + end + + def test_underscore_with_two_words + assert_equal "two_fer", + Generator.new("two-fer").underscore("two-fer") + end + + def test_status + assert_equal "# skip", + Generator.new("acronym").status + end + + def test_status_after_first + generator = Generator.new("acronym") + generator.status + assert_equal "skip", + generator.status + end +end From 8e3afbed42a78928b5e5510dd91f07f4b7ce0734 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 9 Nov 2024 17:50:19 +0100 Subject: [PATCH 03/22] Update gemfile.lock to include toml-rb --- Gemfile.lock | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 86101117a2..1777883b90 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) + citrus (3.0.2) docile (1.4.0) json (2.7.2) minitest (5.22.3) @@ -43,6 +44,9 @@ GEM simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) strscan (3.1.0) + toml-rb (3.0.1) + citrus (~> 3.0, > 3.0) + racc (~> 1.7) unicode-display_width (2.5.0) PLATFORMS @@ -57,6 +61,7 @@ DEPENDENCIES rubocop-minitest rubocop-rake simplecov + toml-rb BUNDLED WITH 2.5.7 From 5c27b4103c1d3b1cd4f166e1b5c34583dff1ba4c Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 9 Nov 2024 17:53:50 +0100 Subject: [PATCH 04/22] Fix rubocop and fix ci --- .github/workflows/generator-tests.yml | 11 +++++------ generatorv2/lib/generator.rb | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index e377705844..f2ce68df88 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -1,12 +1,11 @@ name: GeneratorTests + on: - pull_request: + workflow_dispatch: push: - branches: - - main - schedule: - # Weekly. - - cron: '0 0 * * 0' + branches: [main] + pull_request: + branches: [main] jobs: test-generator-templates: diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb index 4f3fb15f2d..44c2ade978 100644 --- a/generatorv2/lib/generator.rb +++ b/generatorv2/lib/generator.rb @@ -69,7 +69,7 @@ def get_remote_files when Net::HTTPNotFound check_for_local_canonical_data else - raise "Error while requesting the #{@exercise} data file from GitHub... " + + raise "Error while requesting the #{@exercise} data file from GitHub... " \ "Status was #{response.code}" end end From b1f131fa2ea7ad691c2c1e72b259274df22b2848 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 9 Nov 2024 17:57:50 +0100 Subject: [PATCH 05/22] Format files --- generatorv2/lib/generator.rb | 9 +++++---- generatorv2/test/toml_test.rb | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb index 44c2ade978..d54b4b444f 100644 --- a/generatorv2/lib/generator.rb +++ b/generatorv2/lib/generator.rb @@ -12,7 +12,7 @@ def initialize(exercise = nil) end def generate(result_path = "./exercises/practice/#{@exercise}/#{@exercise}_test.rb") - json = get_remote_files + json = remote_files uuid = toml("./exercises/practice/#{@exercise}/.meta/tests.toml") additional_json(json) json["cases"] = remove_tests(uuid, json) @@ -56,11 +56,12 @@ def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml") uuid = TomlRB.load_file(path) uuid.filter do |_k, v| - v.none? { |k, v| k == "include" && !v } - end.map { |k, _v| k } + v.none? { |k, inner_value| k == "include" && !inner_value } + end + uuid.map { |k, _v| k } end - def get_remote_files + def remote_files url = URI.parse("https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/#{@exercise}/canonical-data.json") response = Net::HTTP.get_response(url) case response diff --git a/generatorv2/test/toml_test.rb b/generatorv2/test/toml_test.rb index 8eff369c08..88d42c7abd 100644 --- a/generatorv2/test/toml_test.rb +++ b/generatorv2/test/toml_test.rb @@ -3,12 +3,23 @@ class GeneratorTest < Minitest::Test def test_importning_toml - assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9 6a078f49-c68d-4b7b-89af-33a1a98c28cc], + assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 + 79ae3889-a5c0-4b01-baf0-232d31180c08 + ec7000a7-3931-4a17-890e-33ca2073a548 + 32dd261c-0c92-469a-9c5c-b192e94a63b0 + ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 + 0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9 + 6a078f49-c68d-4b7b-89af-33a1a98c28cc], Generator.new("two_fer").toml("generatorv2/test/misc/tests.toml") end def test_importing_toml_with_no_include - assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 79ae3889-a5c0-4b01-baf0-232d31180c08 ec7000a7-3931-4a17-890e-33ca2073a548 32dd261c-0c92-469a-9c5c-b192e94a63b0 ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 6a078f49-c68d-4b7b-89af-33a1a98c28cc], + assert_equal %w[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 + 79ae3889-a5c0-4b01-baf0-232d31180c08 + ec7000a7-3931-4a17-890e-33ca2073a548 + 32dd261c-0c92-469a-9c5c-b192e94a63b0 + ae2ac9fa-a606-4d05-8244-3bcc4659c1d4 + 6a078f49-c68d-4b7b-89af-33a1a98c28cc], Generator.new("two_fer").toml("generatorv2/test/misc/tests_no_include.toml") end From 700498815c3b0cad20079afe4e3ee64b6fa81f9d Mon Sep 17 00:00:00 2001 From: Victor Goff Date: Sat, 9 Nov 2024 13:48:25 -0500 Subject: [PATCH 06/22] EOL for every line in text file --- .github/workflows/generator-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index f2ce68df88..7e1e283d1b 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -29,4 +29,4 @@ jobs: with: ruby-version: "3.3" bundler-cache: true - run: rake test:generator \ No newline at end of file + run: rake test:generator From 067df32912c6ed09bbecf27574406f9ed6f68166 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 9 Nov 2024 21:58:16 +0100 Subject: [PATCH 07/22] Generator now executable and changes based on feedback --- bin/{generate.rb => generate} | 1 + generatorv2/README.md | 1 + generatorv2/lib/generator.rb | 13 +++---------- 3 files changed, 5 insertions(+), 10 deletions(-) rename bin/{generate.rb => generate} (98%) mode change 100644 => 100755 diff --git a/bin/generate.rb b/bin/generate old mode 100644 new mode 100755 similarity index 98% rename from bin/generate.rb rename to bin/generate index 824ec1e8b1..4e5cc1eaf0 --- a/bin/generate.rb +++ b/bin/generate @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby! require 'optparse' require 'tempfile' require_relative '../generatorv2/lib/generator' diff --git a/generatorv2/README.md b/generatorv2/README.md index 4707dc43ef..21f0ac28c1 100644 --- a/generatorv2/README.md +++ b/generatorv2/README.md @@ -9,6 +9,7 @@ The generator is written in Ruby and is located in the `bin` directory. ### Things to do before running the generator +Run `bundle install` to install the required libraries. Before running the generator you have to make sure a couple of files are in place. 1. `tests.toml` file diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb index d54b4b444f..c62681efac 100644 --- a/generatorv2/lib/generator.rb +++ b/generatorv2/lib/generator.rb @@ -29,14 +29,7 @@ def generate(result_path = "./exercises/practice/#{@exercise}/#{@exercise}_test. end def underscore(str) - str.each_char.reduce("") do |acc, x| - acc << if [' ', '-'].include?(x) - '_' - else - x.downcase - end - acc - end + str.gsub(/[-\s]/, '_').downcase end def camel_case(str) @@ -55,10 +48,10 @@ def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml") raise "Toml not found: #{path}" unless File.exist?(path) uuid = TomlRB.load_file(path) - uuid.filter do |_k, v| + uuid = uuid.filter do |_k, v| v.none? { |k, inner_value| k == "include" && !inner_value } end - uuid.map { |k, _v| k } + uuid.keys end def remote_files From 653f629172a3513cc60a3a10b978761f94937814 Mon Sep 17 00:00:00 2001 From: Victor Goff Date: Sat, 9 Nov 2024 17:50:19 -0500 Subject: [PATCH 08/22] Fix interpreter name --- bin/generate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate b/bin/generate index 4e5cc1eaf0..3eba0dc4c2 100755 --- a/bin/generate +++ b/bin/generate @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby! +#!/usr/bin/env ruby require 'optparse' require 'tempfile' require_relative '../generatorv2/lib/generator' From 39bf79b59f2ebaee7825ec44a178da0b8db93b8b Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:45:07 +0100 Subject: [PATCH 09/22] Add missing name key in ci file --- .github/workflows/generator-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index 7e1e283d1b..75a28331ca 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -17,6 +17,7 @@ jobs: with: ruby-version: "3.3" bundler-cache: true + - name: Verify templates run: ruby ./bin/generator.rb --verify test-generator: name: Check Generator Templates @@ -29,4 +30,5 @@ jobs: with: ruby-version: "3.3" bundler-cache: true + - name: Run tests run: rake test:generator From 7fb52c518ad6d1dddd0f79efc2b56aa9a5e98218 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:47:02 +0100 Subject: [PATCH 10/22] Fix execution path of ci scripts --- .github/workflows/generator-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index 75a28331ca..f617ce0606 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -18,7 +18,7 @@ jobs: ruby-version: "3.3" bundler-cache: true - name: Verify templates - run: ruby ./bin/generator.rb --verify + run: ruby ./bin/generator --verify test-generator: name: Check Generator Templates runs-on: ubuntu-22.04 @@ -31,4 +31,4 @@ jobs: ruby-version: "3.3" bundler-cache: true - name: Run tests - run: rake test:generator + run: bundle exec rake test:generator From e1c69f43528b2f70e3a57a347c07bb17eb259457 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:49:20 +0100 Subject: [PATCH 11/22] Remove Crystal image refernce and fixes to ci --- .github/workflows/generator-tests.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index f617ce0606..a31e681dfa 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -18,13 +18,12 @@ jobs: ruby-version: "3.3" bundler-cache: true - name: Verify templates - run: ruby ./bin/generator --verify + run: ruby ./bin/generate --verify test-generator: name: Check Generator Templates runs-on: ubuntu-22.04 - container: - image: crystallang/crystal steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Ruby uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 with: From 79b00d1d841b6f82bd77eb9789bf28b94a9a15d7 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:59:29 +0100 Subject: [PATCH 12/22] Bump rubocop version and add missing actions checkout --- .github/workflows/generator-tests.yml | 5 ++-- Gemfile | 2 +- Gemfile.lock | 31 +++++++++++----------- exercises/practice/acronym/acronym_test.rb | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index a31e681dfa..ab1f872d43 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -9,9 +9,10 @@ on: jobs: test-generator-templates: - name: Test Generator + name: Check Generator Templates runs-on: ubuntu-22.04 steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Ruby uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 with: @@ -20,7 +21,7 @@ jobs: - name: Verify templates run: ruby ./bin/generate --verify test-generator: - name: Check Generator Templates + name: Test Generator runs-on: ubuntu-22.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 diff --git a/Gemfile b/Gemfile index 22c884c38e..15e36a15a5 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gem 'base64' gem 'minitest' gem 'rake' gem 'mocha', require: false -gem 'rubocop', '~> 1.50.0', require: false +gem 'rubocop', '~> 1.68.0', require: false gem 'rubocop-minitest', require: false gem 'rubocop-rake', require: false gem 'simplecov', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 39da4effde..2ef48e4318 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,34 +2,34 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) - citrus (3.0.2) base64 (0.2.0) + citrus (3.0.2) docile (1.4.0) - json (2.7.2) + json (2.8.1) + language_server-protocol (3.17.0.3) minitest (5.22.3) mocha (2.1.0) ruby2_keywords (>= 0.0.5) - parallel (1.24.0) - parser (3.3.0.5) + parallel (1.26.3) + parser (3.3.6.0) ast (~> 2.4.1) racc racc (1.7.3) rainbow (3.1.1) rake (13.2.1) - regexp_parser (2.9.0) - rexml (3.3.9) - rubocop (1.50.2) + regexp_parser (2.9.2) + rubocop (1.68.0) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + regexp_parser (>= 2.4, < 3.0) + rubocop-ast (>= 1.32.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) + rubocop-ast (1.34.1) + parser (>= 3.3.1.0) rubocop-minitest (0.34.5) rubocop (>= 1.39, < 2.0) rubocop-ast (>= 1.30.0, < 2.0) @@ -43,11 +43,10 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - strscan (3.1.0) toml-rb (3.0.1) citrus (~> 3.0, > 3.0) racc (~> 1.7) - unicode-display_width (2.5.0) + unicode-display_width (2.6.0) PLATFORMS ruby @@ -58,7 +57,7 @@ DEPENDENCIES mocha racc rake - rubocop (~> 1.50.0) + rubocop (~> 1.68.0) rubocop-minitest rubocop-rake simplecov diff --git a/exercises/practice/acronym/acronym_test.rb b/exercises/practice/acronym/acronym_test.rb index 2acf62c9bb..15c65a69b6 100644 --- a/exercises/practice/acronym/acronym_test.rb +++ b/exercises/practice/acronym/acronym_test.rb @@ -30,7 +30,7 @@ def test_punctuation_without_whitespace def test_very_long_abbreviation skip assert_equal 'ROTFLSHTMDCOALM', - Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') + Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') end def test_consecutive_delimiters From f806548b08403ffdfd2d1781b315fc99d243678a Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:03:38 +0100 Subject: [PATCH 13/22] Test adding bundle install --- .github/workflows/generator-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index ab1f872d43..6d79e2bb45 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -18,6 +18,8 @@ jobs: with: ruby-version: "3.3" bundler-cache: true + - name: Verify templates + run: bundle install - name: Verify templates run: ruby ./bin/generate --verify test-generator: From bbad039632acdc58008191dcf922b2f5df5b003d Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:04:48 +0100 Subject: [PATCH 14/22] Test uppdating gemfile --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 15e36a15a5..70b6642d1b 100644 --- a/Gemfile +++ b/Gemfile @@ -3,10 +3,10 @@ source 'https://rubygems.org' gem 'base64' gem 'minitest' gem 'rake' +gem 'toml-rb' gem 'mocha', require: false gem 'rubocop', '~> 1.68.0', require: false gem 'rubocop-minitest', require: false gem 'rubocop-rake', require: false gem 'simplecov', require: false gem 'racc', require: false -gem 'toml-rb', require: false From b6d2e9e55ea99b6ec350a666cf5f85e372b712b9 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:09:46 +0100 Subject: [PATCH 15/22] Change to using `bundle exec` --- .github/workflows/generator-tests.yml | 4 +--- Gemfile | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/generator-tests.yml b/.github/workflows/generator-tests.yml index 6d79e2bb45..f3d112dc0e 100644 --- a/.github/workflows/generator-tests.yml +++ b/.github/workflows/generator-tests.yml @@ -19,9 +19,7 @@ jobs: ruby-version: "3.3" bundler-cache: true - name: Verify templates - run: bundle install - - name: Verify templates - run: ruby ./bin/generate --verify + run: bundle exec ./bin/generate --verify test-generator: name: Test Generator runs-on: ubuntu-22.04 diff --git a/Gemfile b/Gemfile index 70b6642d1b..1217bb5f07 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gem 'base64' gem 'minitest' gem 'rake' -gem 'toml-rb' +gem 'toml-rb', require: false gem 'mocha', require: false gem 'rubocop', '~> 1.68.0', require: false gem 'rubocop-minitest', require: false From 98d267dc35e3033b1059b06ecd31a866efe05dae Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:22:23 +0100 Subject: [PATCH 16/22] Make the generate script use the same rubocop config as the repo --- exercises/practice/acronym/acronym_test.rb | 2 +- generatorv2/lib/generator.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/acronym/acronym_test.rb b/exercises/practice/acronym/acronym_test.rb index 15c65a69b6..2acf62c9bb 100644 --- a/exercises/practice/acronym/acronym_test.rb +++ b/exercises/practice/acronym/acronym_test.rb @@ -30,7 +30,7 @@ def test_punctuation_without_whitespace def test_very_long_abbreviation skip assert_equal 'ROTFLSHTMDCOALM', - Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') + Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') end def test_consecutive_delimiters diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb index c62681efac..47e99cd2fb 100644 --- a/generatorv2/lib/generator.rb +++ b/generatorv2/lib/generator.rb @@ -25,7 +25,7 @@ def generate(result_path = "./exercises/practice/#{@exercise}/#{@exercise}_test. File.write(result_path, result) cli = RuboCop::CLI.new - cli.run(['-x', "--force-default-config", "-o", "/dev/null", result_path]) + cli.run(['-x', "-c", ".rubocop.yml", "-o", "/dev/null", result_path]) end def underscore(str) From 68d467105b68abd2d3f431506e342db6e1154301 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:30:58 +0100 Subject: [PATCH 17/22] Test rollback to rubocop 1.50 --- Gemfile | 2 +- Gemfile.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 1217bb5f07..ad11a6d51d 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ gem 'minitest' gem 'rake' gem 'toml-rb', require: false gem 'mocha', require: false -gem 'rubocop', '~> 1.68.0', require: false +gem 'rubocop', '~> 1.50.0', require: false gem 'rubocop-minitest', require: false gem 'rubocop-rake', require: false gem 'simplecov', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 2ef48e4318..91e05c5536 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,7 +6,6 @@ GEM citrus (3.0.2) docile (1.4.0) json (2.8.1) - language_server-protocol (3.17.0.3) minitest (5.22.3) mocha (2.1.0) ruby2_keywords (>= 0.0.5) @@ -18,14 +17,15 @@ GEM rainbow (3.1.1) rake (13.2.1) regexp_parser (2.9.2) - rubocop (1.68.0) + rexml (3.3.9) + rubocop (1.50.2) json (~> 2.3) - language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.3.0.2) + parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rubocop-ast (>= 1.32.2, < 2.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.34.1) @@ -57,7 +57,7 @@ DEPENDENCIES mocha racc rake - rubocop (~> 1.68.0) + rubocop (~> 1.50.0) rubocop-minitest rubocop-rake simplecov From 76504b537d922ffbc63cc6f2f7482f06956ddab7 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:48:10 +0100 Subject: [PATCH 18/22] Update readme to reflect recent changes --- generatorv2/README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/generatorv2/README.md b/generatorv2/README.md index 21f0ac28c1..920fb7ce43 100644 --- a/generatorv2/README.md +++ b/generatorv2/README.md @@ -26,12 +26,6 @@ By writing after the test name `include = false` and it will be skipped when gen The generator makes sure that the exercise is in the config.json so you need to add it there before running the generator. -3. `spec` directory - -The generator will create a spec file for each exercise, so you need to make sure that the spec directory is in place. -Although there don't have to be any files in the directory, since the script will create one for you. -If it is a file already so will the generator overwrite it. - #### Things to note The script which grabs info from the toml file is quite sensitive, writing the toml file in an incorrect way can brick the generator. @@ -117,7 +111,7 @@ The generator is located in the `bin` directory and is called `generator.rb`. To run the generator so do you have to be in the root directory and run the following command: ```shell -ruby ./bin/generator.rb -e +bundle exec ./bin/generate -e ``` Where `` is the same name as the slug name which is located in the `config.json` file. @@ -125,7 +119,7 @@ Where `` is the same name as the slug name which is located in th For more commands so can you run the following command: ```shell -ruby ./bin/generator.rb --help +bundle exec ./bin/generate --help ``` ### Errors and warnings From 00af4d0ec581789b14bf88eadc714f5c14ffc4ba Mon Sep 17 00:00:00 2001 From: meatball Date: Fri, 15 Nov 2024 19:42:40 +0100 Subject: [PATCH 19/22] Split utils methods into its own module --- generatorv2/lib/generator.rb | 52 +++--------------------------------- generatorv2/lib/utils.rb | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 49 deletions(-) create mode 100644 generatorv2/lib/utils.rb diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb index 47e99cd2fb..ead487ebec 100644 --- a/generatorv2/lib/generator.rb +++ b/generatorv2/lib/generator.rb @@ -4,8 +4,11 @@ require 'json' require 'erb' require 'rubocop' +require_relative 'utils' class Generator + include Utils + def initialize(exercise = nil) @first = true @exercise = exercise @@ -43,53 +46,4 @@ def status end "skip" end - - def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml") - raise "Toml not found: #{path}" unless File.exist?(path) - - uuid = TomlRB.load_file(path) - uuid = uuid.filter do |_k, v| - v.none? { |k, inner_value| k == "include" && !inner_value } - end - uuid.keys - end - - def remote_files - url = URI.parse("https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/#{@exercise}/canonical-data.json") - response = Net::HTTP.get_response(url) - case response - when Net::HTTPSuccess - JSON.parse(response.body) - when Net::HTTPNotFound - check_for_local_canonical_data - else - raise "Error while requesting the #{@exercise} data file from GitHub... " \ - "Status was #{response.code}" - end - end - - def check_for_local_canonical_data(path = "./exercises/practice/#{@exercise}/canonical-data.json") - raise "No canonical-data.json found in #{@exercise} directory" unless File.exist?(path) - - JSON.parse(File.read(path)) - end - - def additional_json(json) - file_path = "./exercises/practice/#{@exercise}/.meta/additional_tests.json" - return unless File.exist?(file_path) - - JSON.parse(File.read(file_path))["cases"].each do |test| - json["cases"] << test - end - end - - def remove_tests(uuid, json) - json["cases"].each_with_object([]) do |x, acc| - if x["cases"] - acc << remove_tests(uuid, json) - elsif uuid.include?(x["uuid"]) - acc << x - end - end - end end diff --git a/generatorv2/lib/utils.rb b/generatorv2/lib/utils.rb new file mode 100644 index 0000000000..d30d11eaf6 --- /dev/null +++ b/generatorv2/lib/utils.rb @@ -0,0 +1,50 @@ +module Utils + def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml") + raise "Toml not found: #{path}" unless File.exist?(path) + + uuid = TomlRB.load_file(path) + uuid = uuid.filter do |_k, v| + v.none? { |k, inner_value| k == "include" && !inner_value } + end + uuid.keys + end + + def remote_files + url = URI.parse("https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/#{@exercise}/canonical-data.json") + response = Net::HTTP.get_response(url) + case response + when Net::HTTPSuccess + JSON.parse(response.body) + when Net::HTTPNotFound + check_for_local_canonical_data + else + raise "Error while requesting the #{@exercise} data file from GitHub... " \ + "Status was #{response.code}" + end + end + + def check_for_local_canonical_data(path = "./exercises/practice/#{@exercise}/canonical-data.json") + raise "No canonical-data.json found in #{@exercise} directory" unless File.exist?(path) + + JSON.parse(File.read(path)) + end + + def additional_json(json) + file_path = "./exercises/practice/#{@exercise}/.meta/additional_tests.json" + return unless File.exist?(file_path) + + JSON.parse(File.read(file_path))["cases"].each do |test| + json["cases"] << test + end + end + + def remove_tests(uuid, json) + json["cases"].each_with_object([]) do |x, acc| + if x["cases"] + acc << remove_tests(uuid, json) + elsif uuid.include?(x["uuid"]) + acc << x + end + end + end +end From 492b44bb27a8c12f7bd6ee8daa4852b88c27b6af Mon Sep 17 00:00:00 2001 From: KOTP Date: Sat, 16 Nov 2024 02:42:31 -0500 Subject: [PATCH 20/22] Breakout helper method and exception class --- bin/generate | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/bin/generate b/bin/generate index 3eba0dc4c2..7fceaaa70a 100755 --- a/bin/generate +++ b/bin/generate @@ -3,6 +3,21 @@ require 'optparse' require 'tempfile' require_relative '../generatorv2/lib/generator' +# Helper methods +def exercises + Dir.entries('./exercises/practice') + .select { |file| File.directory? File.join('./exercises/practice', file) } +end + +class VerificationError < StandardError + def initialize( + message = "VerificationError: The result generated for %s, doesn't match the current file" + ) + super + end +end + +# Parsing Code parser = OptionParser.new parser.on('-v', '--version', 'Print the version') do @@ -14,7 +29,6 @@ parser.on('-h', '--help', 'Prints help') do end parser.on('-a', '--all', 'Generate all exercises') do - exercises = Dir.entries('./exercises/practice').select { |f| File.directory? File.join('./exercises/practice', f) } exercises.each do |exercise| if File.exist?("./exercises/practice/#{exercise}/.meta/test_template.erb") Generator.new(exercise).generate @@ -23,15 +37,16 @@ parser.on('-a', '--all', 'Generate all exercises') do end parser.on('--verify', 'Verify all exercises') do - exercises = Dir.entries('./exercises/practice').select { |f| File.directory? File.join('./exercises/practice', f) } exercises.each do |exercise| if File.exist?("./exercises/practice/#{exercise}/.meta/test_template.erb") current_code = File.read("./exercises/practice/#{exercise}/#{exercise}_test.rb") f = Tempfile.create Generator.new(exercise).generate(f.path) generated_code = f.read - raise RuntimeError.new("The result generated for: #{exercise}, doesnt match the current file") if current_code != generated_code + raise VerificationError unless current_code == generated_code end + rescue VerificationError => e + $stderr.puts e.message % {exercise:} end end From f40f4c4996190db4df3cbbdb818d8adfbb2ad7f2 Mon Sep 17 00:00:00 2001 From: meatball Date: Sun, 17 Nov 2024 19:44:49 +0100 Subject: [PATCH 21/22] Verify now creates a file in exercise directory to get same formatting as the normal creation. Other changes based on feedback --- bin/generate | 5 +++-- exercises/practice/acronym/acronym_test.rb | 3 +-- generatorv2/README.md | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/generate b/bin/generate index 7fceaaa70a..9048fe1fe3 100755 --- a/bin/generate +++ b/bin/generate @@ -40,10 +40,11 @@ parser.on('--verify', 'Verify all exercises') do exercises.each do |exercise| if File.exist?("./exercises/practice/#{exercise}/.meta/test_template.erb") current_code = File.read("./exercises/practice/#{exercise}/#{exercise}_test.rb") - f = Tempfile.create + f = File.new("./exercises/practice/#{exercise}/temp_test.rb", 'w+') Generator.new(exercise).generate(f.path) generated_code = f.read - raise VerificationError unless current_code == generated_code + File.delete(f.path) + fail VerificationError unless current_code == generated_code end rescue VerificationError => e $stderr.puts e.message % {exercise:} diff --git a/exercises/practice/acronym/acronym_test.rb b/exercises/practice/acronym/acronym_test.rb index 2acf62c9bb..7dde064850 100644 --- a/exercises/practice/acronym/acronym_test.rb +++ b/exercises/practice/acronym/acronym_test.rb @@ -29,8 +29,7 @@ def test_punctuation_without_whitespace def test_very_long_abbreviation skip - assert_equal 'ROTFLSHTMDCOALM', - Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') + assert_equal 'ROTFLSHTMDCOALM', Acronym.abbreviate('Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me') end def test_consecutive_delimiters diff --git a/generatorv2/README.md b/generatorv2/README.md index 920fb7ce43..ec18002842 100644 --- a/generatorv2/README.md +++ b/generatorv2/README.md @@ -32,21 +32,21 @@ The script which grabs info from the toml file is quite sensitive, writing the t Here are some examples of how you should **NOT** work with the toml file. -Make sure that the uuid is the only thing inside of `[uuid]`, if there is, for example, an extra space so would that break it. +Make sure that the UUID is the only thing inside of `[UUID]`, if there is, for example, an extra space so would that break it. Here is an example ```toml -# This would break it since it is an extra space between uuid and `]` +# This would break it since it is an extra space between UUID and `]` [1e22cceb-c5e4-4562-9afe-aef07ad1eaf4 ] -# This would break it since it is an extra space between uuid and `[` +# This would break it since it is an extra space between UUID and `[` [ 1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] ``` -The script won't care if you write `include = true` since if it sees the uuid it will always take it as long as `include = false` is not written. +The script won't care if you write `include = true` since if it sees the UUID it will always take it as long as `include = false` is not written. The script will not work if anything is misspelled, although the part which gets `include = false` doesn't care if it gets an extra space or not. **NOTE:** -You are also **NOT** allowed to write `include = false` more than once after each uuid. +You are also **NOT** allowed to write `include = false` more than once after each UUID. Since that can lead to errors in the generator. Bad way: @@ -78,7 +78,7 @@ The template file is written in [Embedded Ruby(ERB)][erb]. ERB enables you to write Ruby code inside of the template file. It also means that the templates can be highly customizable since you can write any Ruby code you want. -When writing the template file it is recommended to look at already existing template files to get a better understanding of how it works. +When writing the template file, it is recommended to look at already existing template files to get a better understanding of how it works. The template is getting a slightly modified version of the canonical data, so you can check out the [canonical data][canonical data] to see the data structure. The modification is that the cases which are not included in the toml file will be removed from the data structure. From 769dee3702cd8eee4e2aa91711bfb7c887915720 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:36:45 +0100 Subject: [PATCH 22/22] Changes based on feedback and add more tasks to rakefile --- Rakefile | 15 +++++++++++++++ bin/generate | 8 ++++---- generatorv2/lib/generator.rb | 7 +++---- generatorv2/lib/utils.rb | 6 ++++++ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Rakefile b/Rakefile index fe00a092b5..013376fdac 100644 --- a/Rakefile +++ b/Rakefile @@ -16,6 +16,21 @@ task :rubocop do system('rubocop --display-cop-names') end +desc "Run generator for specefic exercise" +task :generate, [:exercise] do |_t, argumments| + system("./bin/generate --exercise #{argumments[:exercise]}") +end + +desc "Run generator for all exercises" +task :generate_all do + system("./bin/generate --all") +end + +desc "Verify templates for all exercises" +task :verify do + system("./bin/generate --verify") +end + namespace :test do flags = ARGV.drop_while { |e| e != '--' }.drop(1).join(' ') diff --git a/bin/generate b/bin/generate index 9048fe1fe3..23b655e0fd 100755 --- a/bin/generate +++ b/bin/generate @@ -10,9 +10,9 @@ def exercises end class VerificationError < StandardError - def initialize( - message = "VerificationError: The result generated for %s, doesn't match the current file" - ) + MESSAGE = 'The result generated for %s, does not match the current file' + + def initialize(message = MESSAGE) super end end @@ -47,7 +47,7 @@ parser.on('--verify', 'Verify all exercises') do fail VerificationError unless current_code == generated_code end rescue VerificationError => e - $stderr.puts e.message % {exercise:} + stderr.puts e.message % {exercise:} end end diff --git a/generatorv2/lib/generator.rb b/generatorv2/lib/generator.rb index ead487ebec..042bcbda6e 100644 --- a/generatorv2/lib/generator.rb +++ b/generatorv2/lib/generator.rb @@ -8,6 +8,7 @@ class Generator include Utils + include NullDevice def initialize(exercise = nil) @first = true @@ -20,15 +21,13 @@ def generate(result_path = "./exercises/practice/#{@exercise}/#{@exercise}_test. additional_json(json) json["cases"] = remove_tests(uuid, json) status = proc { status } - camel_case = proc { |str| camel_case(str) } - underscore = proc { |str| underscore(str) } template = ERB.new File.read("./exercises/practice/#{@exercise}/.meta/test_template.erb") result = template.result(binding) File.write(result_path, result) - cli = RuboCop::CLI.new - cli.run(['-x', "-c", ".rubocop.yml", "-o", "/dev/null", result_path]) + RuboCop::CLI.new. + run(['-x', '-c', '.rubocop.yml', '-o', NullDevice.path, result_path]) end def underscore(str) diff --git a/generatorv2/lib/utils.rb b/generatorv2/lib/utils.rb index d30d11eaf6..04a9f87e4e 100644 --- a/generatorv2/lib/utils.rb +++ b/generatorv2/lib/utils.rb @@ -1,3 +1,9 @@ +module NullDevice + def self.path + Gem.win_platform? ? 'NUL' : '/dev/null' + end +end + module Utils def toml(path = "./exercises/practice/#{@exercise}/.meta/tests.toml") raise "Toml not found: #{path}" unless File.exist?(path)