diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..88e6cb8 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +name: Test +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2.2 + - name: Cache gems + uses: actions/cache@v1 + with: + path: vendor/bundle + key: ${{ runner.os }}-rubydeps-${{ hashFiles('Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-rubydeps- + - name: Install gems + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + - name: Run Rubocop + run: bundle exec rubocop --parallel + - name: Run tests + run: bundle exec rake test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97f1a26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +inputs/*.txt +.session diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..82a04cb --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,282 @@ +AllCops: + TargetRubyVersion: 3.2.2 + SuggestExtensions: false + NewCops: enable + Exclude: + - 'bin/*' + - 'vendor/**/*' +Layout/ArgumentAlignment: + Enabled: true + EnforcedStyle: with_fixed_indentation +Metrics/PerceivedComplexity: + Enabled: true + Max: 50 +Metrics/CyclomaticComplexity: + Enabled: true + Max: 50 +Layout/ClosingParenthesisIndentation: + Enabled: false +Layout/EmptyLinesAroundClassBody: + Enabled: false +Layout/EmptyLinesAroundModuleBody: + Enabled: false +Layout/EmptyLinesAroundBlockBody: + Enabled: false +Layout/EmptyLinesAroundAttributeAccessor: + Enabled: true +Layout/FirstHashElementIndentation: + Enabled: true + EnforcedStyle: consistent +Layout/LineLength: + Max: 120 +Layout/MultilineMethodCallBraceLayout: + Enabled: false +Layout/ParameterAlignment: + Enabled: false + EnforcedStyle: with_fixed_indentation +Layout/SpaceAroundMethodCallOperator: + Enabled: true +Lint/DeprecatedOpenSSLConstant: + Enabled: true +Lint/RaiseException: + Enabled: true +Lint/StructNewOverride: + Enabled: true +Metrics/BlockLength: + Exclude: + - 'Rakefile' + - '**/*.rake' + - 'spec/**/*.rb' + - '*.gemspec' + Max: 100 +Metrics/MethodLength: + Enabled: true + Max: 100 + Exclude: + - '**/*.rake' +Metrics/ModuleLength: + Enabled: true + Exclude: + - 'spec/**/*.rb' +Metrics/AbcSize: + Max: 100 +Metrics/ClassLength: + Enabled: false +Metrics/ParameterLists: + Enabled: true +Style/ClassAndModuleChildren: + Enabled: false +Style/Documentation: + Enabled: false +Style/EmptyMethod: + Enabled: false +Style/ExponentialNotation: + Enabled: true +Style/FrozenStringLiteralComment: + Enabled: false +Style/GuardClause: + MinBodyLength: 3 +Style/HashEachMethods: + Enabled: true +Style/HashTransformKeys: + Enabled: true +Style/HashTransformValues: + Enabled: true +Style/RedundantReturn: + Enabled: false +Style/SlicingWithRange: + Enabled: true +Style/SymbolArray: + Enabled: false +Style/WordArray: + Enabled: false +Lint/DuplicateElsifCondition: + Enabled: true +Lint/MixedRegexpCaptureTypes: + Enabled: true +Style/AccessorGrouping: + Enabled: true +Style/ArrayCoercion: + Enabled: true +Style/BisectedAttrAccessor: + Enabled: true +Style/CaseLikeIf: + Enabled: true +Style/HashAsLastArrayItem: + Enabled: true +Style/HashLikeCase: + Enabled: true +Style/RedundantAssignment: + Enabled: true +Style/RedundantFetchBlock: + Enabled: true +Style/RedundantFileExtensionInRequire: + Enabled: true +Style/RedundantRegexpCharacterClass: + Enabled: true +Style/RedundantRegexpEscape: + Enabled: true +Lint/BinaryOperatorWithIdenticalOperands: + Enabled: true +Lint/DuplicateRescueException: + Enabled: true +Lint/EmptyConditionalBody: + Enabled: true +Lint/FloatComparison: + Enabled: true +Lint/MissingSuper: + Enabled: true +Lint/OutOfRangeRegexpRef: + Enabled: true +Lint/SelfAssignment: + Enabled: true +Lint/TopLevelReturnWithArgument: + Enabled: true +Lint/UnreachableLoop: + Enabled: true +Style/ExplicitBlockArgument: + Enabled: true +Style/GlobalStdStream: + Enabled: true +Style/OptionalBooleanParameter: + Enabled: true +Style/SingleArgumentDig: + Enabled: true +Style/StringConcatenation: + Enabled: true +Layout/BeginEndAlignment: # (new in 0.91) + Enabled: true +Lint/ConstantDefinitionInBlock: # (new in 0.91) + Enabled: true +Lint/DuplicateRequire: # (new in 0.90) + Enabled: true +Lint/EmptyFile: # (new in 0.90) + Enabled: true +Lint/HashCompareByIdentity: # (new in 0.93) + Enabled: true +Lint/IdentityComparison: # (new in 0.91) + Enabled: true +Lint/RedundantSafeNavigation: # (new in 0.93) + Enabled: true +Lint/TrailingCommaInAttributeDeclaration: # (new in 0.90) + Enabled: true +Lint/UselessMethodDefinition: # (new in 0.90) + Enabled: true +Lint/UselessTimes: # (new in 0.91) + Enabled: true +Style/ClassEqualityComparison: # (new in 0.93) + Enabled: true +Style/CombinableLoops: # (new in 0.90) + Enabled: true +Style/KeywordParametersOrder: # (new in 0.90) + Enabled: true +Style/RedundantSelfAssignment: # (new in 0.90) + Enabled: true +Style/SoleNestedConditional: # (new in 0.89) + Enabled: true +Layout/SpaceBeforeBrackets: # (new in 1.7) + Enabled: true +Lint/AmbiguousAssignment: # (new in 1.7) + Enabled: true +Lint/DeprecatedConstants: # (new in 1.8) + Enabled: true +Lint/DuplicateBranch: # (new in 1.3) + Enabled: true +Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1) + Enabled: true +Lint/EmptyBlock: # (new in 1.1) + Enabled: true +Lint/EmptyClass: # (new in 1.3) + Enabled: true +Lint/LambdaWithoutLiteralBlock: # (new in 1.8) + Enabled: true +Lint/NoReturnInBeginEndBlocks: # (new in 1.2) + Enabled: true +Lint/RedundantDirGlobSort: # (new in 1.8) + Enabled: true +Lint/ToEnumArguments: # (new in 1.1) + Enabled: true +Lint/UnexpectedBlockArity: # (new in 1.5) + Enabled: true +Lint/UnmodifiedReduceAccumulator: # (new in 1.1) + Enabled: true +Style/ArgumentsForwarding: # (new in 1.1) + Enabled: true +Style/CollectionCompact: # (new in 1.2) + Enabled: true +Style/DocumentDynamicEvalDefinition: # (new in 1.1) + Enabled: true +Style/EndlessMethod: # (new in 1.8) + Enabled: true +Style/HashExcept: # (new in 1.7) + Enabled: true +Style/NegatedIfElseCondition: # (new in 1.2) + Enabled: true +Style/NilLambda: # (new in 1.3) + Enabled: true +Style/RedundantArgument: # (new in 1.4) + Enabled: true +Style/SwapValues: # (new in 1.1) + Enabled: true +Lint/NumberedParameterAssignment: # (new in 1.9) + Enabled: true +Lint/OrAssignmentToConstant: # (new in 1.9) + Enabled: true +Lint/SymbolConversion: # (new in 1.9) + Enabled: true +Lint/TripleQuotes: # (new in 1.9) + Enabled: true +Style/IfWithBooleanLiteralBranches: # (new in 1.9) + Enabled: true +Gemspec/DeprecatedAttributeAssignment: # (new in 1.10) + Enabled: true +Style/HashConversion: # (new in 1.10) + Enabled: true +Layout/LineEndStringConcatenationIndentation: # new in 1.18 + Enabled: true +Lint/AmbiguousOperatorPrecedence: # new in 1.21 + Enabled: true +Lint/AmbiguousRange: # new in 1.19 + Enabled: true +Lint/EmptyInPattern: # new in 1.16 + Enabled: true +Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 + Enabled: true +Lint/RequireRelativeSelfPath: # new in 1.22 + Enabled: true +Security/IoMethods: # new in 1.22 + Enabled: true +Style/InPatternThen: # new in 1.16 + Enabled: true +Style/MultilineInPatternThen: # new in 1.16 + Enabled: true +Style/NumberedParameters: # new in 1.22 + Enabled: true +Style/NumberedParametersLimit: # new in 1.22 + Enabled: true +Style/QuotedSymbols: # new in 1.16 + Enabled: true +Style/RedundantSelfAssignmentBranch: # new in 1.19 + Enabled: true +Style/SelectByRegexp: # new in 1.22 + Enabled: true +Style/StringChars: # new in 1.12 + Enabled: true +Gemspec/RequireMFA: # new in 1.23 + Enabled: true +Lint/UselessRuby2Keywords: # new in 1.23 + Enabled: true +Naming/BlockForwarding: # new in 1.24 + Enabled: true +Style/FileRead: # new in 1.24 + Enabled: true +Style/FileWrite: # new in 1.24 + Enabled: true +Style/MapToHash: # new in 1.24 + Enabled: true +Style/NestedFileDirname: # new in 1.26 + Enabled: true +Style/OpenStructUse: # new in 1.23 + Enabled: false +Style/NumericLiterals: + Enabled: false diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..be94e6f --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.2.2 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..f2d469e --- /dev/null +++ b/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'debug' +gem 'minitest' +gem 'rake' +gem 'rubocop' +gem 'solargraph' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..053f98b --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,94 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + backport (1.2.0) + benchmark (0.3.0) + debug (1.8.0) + irb (>= 1.5.0) + reline (>= 0.3.1) + diff-lcs (1.5.0) + e2mmap (0.1.0) + io-console (0.6.0) + irb (1.9.1) + rdoc + reline (>= 0.3.8) + jaro_winkler (1.5.6) + json (2.7.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + language_server-protocol (3.17.0.3) + minitest (5.20.0) + nokogiri (1.15.5-arm64-darwin) + racc (~> 1.4) + nokogiri (1.15.5-x64-mingw-ucrt) + racc (~> 1.4) + parallel (1.23.0) + parser (3.2.2.4) + ast (~> 2.4.1) + racc + psych (5.1.1.1) + stringio + racc (1.7.3) + rainbow (3.1.1) + rake (13.1.0) + rbs (2.8.4) + rdoc (6.6.0) + psych (>= 4.0.0) + regexp_parser (2.8.2) + reline (0.4.1) + io-console (~> 0.5) + reverse_markdown (2.1.1) + nokogiri + rexml (3.2.6) + rubocop (1.58.0) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.2.2.4) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.30.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.30.0) + parser (>= 3.2.1.0) + ruby-progressbar (1.13.0) + solargraph (0.49.0) + backport (~> 1.2) + benchmark + bundler (~> 2.0) + diff-lcs (~> 1.4) + e2mmap + jaro_winkler (~> 1.5) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + parser (~> 3.0) + rbs (~> 2.0) + reverse_markdown (~> 2.0) + rubocop (~> 1.38) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + stringio (3.1.0) + thor (1.3.0) + tilt (2.3.0) + unicode-display_width (2.5.0) + yard (0.9.34) + +PLATFORMS + arm64-darwin-21 + x64-mingw-ucrt + +DEPENDENCIES + debug + minitest + rake + rubocop + solargraph + +BUNDLED WITH + 2.3.22 diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd37a57 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Advent of Code + +Ruby solutions to [Advent of Code](https://adventofcode.com/). + +## Requirements + +Install ruby 3.2.2, bundler, and the gems. + +``` +bundle install +``` + +## Use + +``` +# solve all +rake solve:all + +# solve a specific day +rake solve:7 + +# run all tests +rake test + +# run a specific test +ruby ./test/07_test.rb +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..a361f4b --- /dev/null +++ b/Rakefile @@ -0,0 +1,72 @@ +require 'rake/testtask' +require './app' +require 'net/http' + +namespace :solve do + + task :all do + runner = Aoc::Runner.new + (1..25).each { |i| runner.call(i) } + end + + (1..25).each do |i| + desc "Run day #{i}" + task i.to_s.to_sym do + Aoc::Runner.new.call(i) + end + end +end + +namespace :scaffold do + (1..25).each do |i| + task i.to_s.to_sym do + puts "🎁 Generate code for day #{i}..." + + number = i.to_s.rjust(2, '0') + marker = '{DAY_NUMBER}' + + ruby_file = File.join(APP_ROOT, 'lib', 'days', "#{number}.rb") + # raise '⛔ Stopping: Already exists' if File.exists?(ruby_file) + + File.write(ruby_file, File.read(File.join(APP_ROOT, 'template', 'day')).sub(marker, number)) + File.write( + File.join(APP_ROOT, 'test', "#{number}_test.rb"), + File.read(File.join(APP_ROOT, 'template', 'test')).gsub(marker, number) + ) + File.write(File.join(APP_ROOT, 'examples', "#{number}.txt"), '') + + end + end +end + +namespace :download do + + def download_input(number) + session_path = File.join(APP_ROOT, '.session') + return '' unless File.exist?(session_path) + + puts "⬇️ Download day #{number} input" + + response = Net::HTTP.get_response( + URI("https://adventofcode.com/2022/day/#{number}/input"), + { 'Cookie' => "session=#{File.read(session_path)}" } + ) + + if response.code == '200' + response.body + else + puts "💥 Download failed - #{response.message}" + end + end + + (1..25).each do |i| + task i.to_s.to_sym do + number = i.to_s.rjust(2, '0') + File.write(File.join(APP_ROOT, 'inputs', "#{number}.txt"), download_input(i)) + end + end +end + +Rake::TestTask.new do |t| + t.pattern = 'test/*_test.rb' +end diff --git a/app.rb b/app.rb new file mode 100644 index 0000000..3bf12de --- /dev/null +++ b/app.rb @@ -0,0 +1,7 @@ +APP_ROOT = __dir__ + +require 'debug' +require 'rubygems' +require 'bundler/setup' +require './lib/runner' +Dir['./lib/days/*.rb'].each { |f| require f } diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..51e10c4 --- /dev/null +++ b/bin/rake @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rake' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rake", "rake") diff --git a/bin/rake.cmd b/bin/rake.cmd new file mode 100755 index 0000000..413267a --- /dev/null +++ b/bin/rake.cmd @@ -0,0 +1,30 @@ +@ruby -x "%~f0" %* +@exit /b %ERRORLEVEL% + +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rake' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rake", "rake") diff --git a/bin/rubocop b/bin/rubocop new file mode 100755 index 0000000..2b1fa1f --- /dev/null +++ b/bin/rubocop @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rubocop' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rubocop", "rubocop") diff --git a/bin/rubocop.cmd b/bin/rubocop.cmd new file mode 100755 index 0000000..46028af --- /dev/null +++ b/bin/rubocop.cmd @@ -0,0 +1,30 @@ +@ruby -x "%~f0" %* +@exit /b %ERRORLEVEL% + +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rubocop' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rubocop", "rubocop") diff --git a/examples/01.text b/examples/01.text new file mode 100644 index 0000000..e69de29 diff --git a/inputs/.keep b/inputs/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/days/01.rb b/lib/days/01.rb new file mode 100644 index 0000000..9fc4372 --- /dev/null +++ b/lib/days/01.rb @@ -0,0 +1,11 @@ +class Day01 + + def part1(input) + 'TODO' + end + + def part2(input) + 'TODO' + end + +end diff --git a/lib/runner.rb b/lib/runner.rb new file mode 100644 index 0000000..d88e509 --- /dev/null +++ b/lib/runner.rb @@ -0,0 +1,39 @@ +module Aoc + class Runner + + def call(number) + day = get_day_class(number) + return unless day + + input_path = File.join(APP_ROOT, 'inputs', "#{number.to_s.rjust(2, '0')}.txt") + raise 'Input does not exist' unless File.exist?(input_path) + + input_data = File.read(input_path).rstrip + + puts <<~HERE + Day #{number} #{emoji_for(number)}#{' '} + - 1: #{day.part1(input_data)} + - 2: #{day.part2(input_data)} + HERE + end + + private + + def get_day_class(number) + Object.const_get("Day#{number.to_s.rjust(2, '0')}").new + rescue NameError + # puts "Day #{number} not implemented" + end + + def emoji_for(number) + case number % 3 + when 0 + '🎄' + when 1 + '❄️' + when 2 + '🎁' + end + end + end +end diff --git a/template/day b/template/day new file mode 100644 index 0000000..e2c8058 --- /dev/null +++ b/template/day @@ -0,0 +1,11 @@ +class Day{DAY_NUMBER} + + def part1(input) + 'TODO' + end + + def part2(input) + 'TODO' + end + +end diff --git a/template/test b/template/test new file mode 100644 index 0000000..1ca589e --- /dev/null +++ b/template/test @@ -0,0 +1,17 @@ +require 'minitest/autorun' +require_relative '../app' + +class TestDay{DAY_NUMBER} < Minitest::Test + def setup + @data = File.read(File.join(APP_ROOT, 'examples', '{DAY_NUMBER}.txt')).rstrip + @day = Day{DAY_NUMBER}.new + end + + def test_part1 + assert_equal @day.part1(@data), '' + end + + def test_part2 + assert_equal @day.part2(@data), '' + end +end diff --git a/test/01_test.rb b/test/01_test.rb new file mode 100644 index 0000000..a0d58de --- /dev/null +++ b/test/01_test.rb @@ -0,0 +1,17 @@ +require 'minitest/autorun' +require_relative '../app' + +class TestDay01 < Minitest::Test + def setup + @data = File.read(File.join(APP_ROOT, 'examples', '01.txt')).rstrip + @day = Day01.new + end + + def test_part1 + assert_equal @day.part1(@data), '' + end + + def test_part2 + assert_equal @day.part2(@data), '' + end +end