diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c84e79c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,77 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + shellcheck: + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - uses: actions/checkout@v4 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@2.0.0 + env: + SHELLCHECK_OPTS: --severity style --enable all --exclude SC2312 --shell bash + with: + check_together: yes + scandir: "./scripts" + + test: + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - uses: actions/checkout@v4 + + - name: Get JDK version + run: | + source scripts/get_java_version.sh + echo "JAVA_VERSION=${JAVA_VERSION}" >> "${GITHUB_ENV}" + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: "temurin" + - uses: sbt/setup-sbt@v1 + + - name: Run tests + run: sbt test + + lint: + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - uses: actions/checkout@v4 + + - name: Get JDK version + run: | + source scripts/get_java_version.sh + echo "JAVA_VERSION=${JAVA_VERSION}" >> "${GITHUB_ENV}" + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: "temurin" + - uses: sbt/setup-sbt@v1 + + - name: Check + run: sbt scalafmtSbtCheck scalafmtCheck Test/scalafmtCheck + + build-dockerfile: + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - uses: actions/checkout@v4 + + - name: Build Dockerfile + run: docker build --file "./Dockerfile" "." diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ded7441 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +output/* +logs +target +/.idea +/.idea_modules +/.classpath +/.project +/.settings +/RUNNING_PID + +bin/ +.eclipse +/lib/ +/logs/ +/modules +/project/target +/target +project/project/ +tmp/ +test-result +server.pid +*.eml +/dist/ +.cache +*.rdb +.DS_Store +.tag* +app/services/redis/ +/logs/application.log +.sqitch/sqitch.conf +.sqitch/templates +.pgpass* +db/data +.ecr_token +.bsp +*.bloop +.metals +.vscode +metals.sbt +*.log diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000..7ed7914 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,23 @@ +pull_request_rules: + +- name: Automatically approve bot PRs + conditions: &base_merge_conditions + - status-success=shellcheck + - status-success=test + - status-success=lint + - status-success=build-dockerfile + - or: + - author=horothesun-scala-steward[bot] + - author=horothesun-renovate[bot] + actions: + review: + type: APPROVE + +- name: Automatically merge bot PRs + conditions: + - and: *base_merge_conditions + - and: + - "#approved-reviews-by>=1" + actions: + merge: + method: squash diff --git a/.scala-steward.conf b/.scala-steward.conf new file mode 100644 index 0000000..4400ac4 --- /dev/null +++ b/.scala-steward.conf @@ -0,0 +1 @@ +reviewers = [ "horothesun" ] diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..d1ec73a --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,17 @@ +version = 3.8.3 +runner.dialect = scala3 +align.preset = some +align.arrowEnumeratorGenerator = true +align.openParenCallSite = true +danglingParentheses.preset = true +includeCurlyBraceInSelectChains = false +maxColumn = 120 +newlines.penalizeSingleSelectMultiArgList = false +optIn.breakChainOnFirstMethodDot = true +optIn.configStyleArguments = true +project.git = true +rewrite.rules = [ Imports, RedundantBraces, RedundantParens, PreferCurlyFors ] +rewrite.imports.sort = scalastyle +rewrite.imports.groups = [ [".*"] ] +runner.optimizer.forceConfigStyleOnOffset = 80 +style = IntelliJ # for pretty alignment diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ef9b7c9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +# The only purpose of this file is to get Renovate updates on the Temurin JDK version. +# The CI uses the following JAVA_VERSION value to configure its JDK setup (through `scripts/get_java_version.sh`). + +ARG JAVA_VERSION=21 + +FROM eclipse-temurin:${JAVA_VERSION} diff --git a/README.md b/README.md new file mode 100644 index 0000000..11e636e --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Advent of Code 2024 + +[![CI](https://github.com/horothesun/advent-of-code-2024/actions/workflows/ci.yml/badge.svg)](https://github.com/horothesun/advent-of-code-2024/actions/workflows/ci.yml) +[![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg?style=flat-square)](https://renovatebot.com) +[![Mergify enabled](https://img.shields.io/badge/Mergify-enabled-success.svg?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjA8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjMyPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT4xPC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4zMjwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxkYzpzdWJqZWN0PgogICAgICAgICAgICA8cmRmOkJhZy8+CiAgICAgICAgIDwvZGM6c3ViamVjdD4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTktMDUtMjRUMTg6MDU6MjQ8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPlBpeGVsbWF0b3IgMy44LjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CpNRBfcAAAXOSURBVFgJxVddbFRFFD4z9+5PS3+E2pTyU6CitfxoUCSiDyRoVEjUh4bKkxbwzcSo8S8oMYRoYnw0PviAFDQRIZgYE4PRkGhihIJgkLZUKlQEC9SltNt0u3v//L5p73bb3aWURDnZuzN35pzznXPmzJm5SqZBi79JVNhDyaXK0itUIC2ByAMUVyJHAyWtgeefcMvK27vXVw3eqFrITk2NBy48qJQ8FwTB4xIEi3QkLoHn4HGNsLJsUVZEfGcE1qhzSqlvg0B2dzbNOzyV9usasGxvT6MXsd8BU5OKxOzAzQDUg074XpAUDLFE2VEJnLQLrgOW424/tXFhZ0F2DBY1oPHAX1uU0u8rO1LlZ+AZXJoWIWQ6iki5TiII/Dc6m+bvLCRf0IC79/e8q+3YVrg75nEh0RsbY0QQFvHd9HunNyx8a7JUngEGPFqyFSGcvteTtYfviAaWUPxMKs+ICQYs+aJns0RjO5lg0w55CFaspRFIVMmkt3Q8s/CTkC1rQMO+sw2WFflZlJ4ZZnfIxJYZkGXOnZhGn7tFAr/f85zVXc31XRTVobxW1g6EKQ/cA7LjB4YxjRd0xcUfx8K8zB0jfzGiY8QgVshjnFq67+yqwIr+hGy1s1rBQWW1pZa8ds9MmV1iyZG+EWn9PSk16Ee0knNJR65lfJkV0zKn1Ja4peTSsCe9KdfMF4wYl0JpV3mZh9ub69sQE0RFWZvNPk8Ph4Zl220rZknXtYx82H7NGPL1Y7XGADKc6s9IB56HauIydwaKEcYSaU92w8iPTw8K7MknhE1FUVN8bzMm23TDV33lcPQJFplcYngrIlrmwrM9Z5JyrC8tX54bkuq4JZ0waACeL5sZleb6MjPWPeDI+SFXqmKWvLL8NlkzOy5cskJkChowia21O7RUtK4brXDj7IgUQDzpGsjIi8sq5dG5pdJyV7lsP3FV1h3slTePJkxiplDvWn68Ik9912ueI1dQtECPgN8fVzehZ7CASWyN+N+vIzHA5VurYcUOAN5XFZO9a2skMeLLZ4gGAiM9Sdcko41+P8LOxOzH/K8J1A8QI1VwCcwskpqYxJZAt0wOv+HBH/LMgJ4FGOnvYdckJpeWcyFZTKyxsdANzuewhKzZ1mACW4Nr5eTwZ7nGlIaeTKU0V26qvsEEdrYOTCXwX81jCYJj5sAogsAiEyYz+2GIwzGK8dwlsQnHycunGBnMQI4hAmoXz+9CRAVVcS315aZcmGJjY2EJUod9z2LEZzHmaQTn6spGedmyQBUzwmAqv1WL9o77Thri+SnjoxiwEB1HZm88dBnGWNKEfX872peXV8rrbQl56fA/ZpuyEjYtKkPFtA0vt+PbkKWOfFK4PQFTBb/Yvl3WrjPD53FQLMg9hChXGbWkoTIqLT9clu5BBxGwZE1tifSlPPPsOTNoKve6eaVy76yorEZF/PyPpHx/cRjb1JHWNTVGx5Djo/yOm8HwA+u8HwV219PVScwdnLwMFBiE4EVsvWfvLJeV1THj4cmrGTmDqlcLj5+smyHr58+QOyoichoF67eracNDXspQljpywWkGsYg5hi1yo4dRGw6jjzoGZAQLvnZOiWxqqDCn5KfdSTl4YdgcRi8sqZRV1XG5hCh9cLJfenE4hdvYxIA1I+cwygamcf+f+3SsZIOfTo3HCj0mHNcximQbwUsU2iiUQXbZVIa+MzbP1c6Ah6ci51lJJ4BjHhgCjP2dGxY049U4wBYg3jZcw/rNpcGMjP5RATOddT02Bs4ZGoSfCS/7JP6Th7yUmQxO3cQgFvlJ2UJkbiiu9ypuRKNaR+ez/6MQ2dfrdgryMhGoGxjhbYhKsgbwhXc13l55gczLHDLcLHGpeCmF7tz7INUVNPb/vJZPiEDoIO/vWKvnYV+CSXNT0YDXRhY6qKvQN0HRCISG3NJPs9AItrfs4zTXCPb5eR5JpZbg2FuBzbYJ+37881z0LmTUCaekpGM6n+f/AiQDwkIqcbgQAAAAAElFTkSuQmCC)](https://mergify.com) +[![Scala](https://img.shields.io/badge/Scala-3-%23DC322F?style=flat&labelColor=%23383838&logo=Scala&logoColor=%23DC322F&logoWidth=12&cacheSeconds=3600)](https://www.scala-lang.org/) + +Initialise a new day by running + +```bash +scripts/init_day.sh +``` + +Test specific day with + +```bash +sbt "~testOnly *DayXSuite*" +``` diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..5faf20d --- /dev/null +++ b/build.sbt @@ -0,0 +1,59 @@ +val catsVersion = "2.12.0" + +val kittensVersion = "3.4.0" + +val catsParseVersion = "1.0.0" + +val catsEffectVersion = "3.5.7" + +val fs2Version = "3.11.0" + +val drosteVersion = "0.9.0" + +val munitVersion = "1.0.2" + +val munitScalacheckVersion = "1.0.0" + +val munitCatsEffectVersion = "2.0.0" + +val scalacheckVersion = "1.18.1" + +val scalacheckEffectMunitVersion = "1.0.4" + +val disciplineMunitVersion = "2.0.0" + +lazy val root = project + .in(file(".")) + .settings( + organization := "com.horothesun", + name := "advent-of-code-2023", + scalaVersion := "3.5.2", + libraryDependencies ++= Seq( + "org.typelevel" %% "cats-core" % catsVersion, + "org.typelevel" %% "kittens" % kittensVersion, + "org.typelevel" %% "cats-parse" % catsParseVersion, + "org.typelevel" %% "cats-effect" % catsEffectVersion, + "co.fs2" %% "fs2-core" % fs2Version, + "io.higherkindness" %% "droste-core" % drosteVersion, + "org.scalameta" %% "munit" % munitVersion % Test, + "org.typelevel" %% "munit-cats-effect" % munitCatsEffectVersion % Test, + "org.scalameta" %% "munit-scalacheck" % munitScalacheckVersion % Test, + "org.scalacheck" %% "scalacheck" % scalacheckVersion % Test, + "org.typelevel" %% "scalacheck-effect-munit" % scalacheckEffectMunitVersion % Test, + "org.typelevel" %% "cats-effect-testkit" % catsEffectVersion % Test, + "org.typelevel" %% "cats-laws" % catsVersion % Test, + "org.typelevel" %% "discipline-munit" % disciplineMunitVersion % Test + ), + scalacOptions ++= Seq( + "-deprecation", + "-encoding", + "UTF-8", + "-feature", + "-unchecked", + "-language:postfixOps", + "-source:future", + "-explain", + "-Wvalue-discard", + "-Wunused:all" + ) + ) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..e88a0d8 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.10.6 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..7d517ef --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") diff --git a/scripts/get_java_version.sh b/scripts/get_java_version.sh new file mode 100755 index 0000000..ef5aea0 --- /dev/null +++ b/scripts/get_java_version.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +JAVA_VERSION=$(grep "ARG JAVA_VERSION=" "Dockerfile" | grep -o "[^=]*$") + +[[ -z "${JAVA_VERSION}" ]] && echo "Error: JAVA_VERSION not found in Dockerfile" && exit 123 + +export JAVA_VERSION diff --git a/scripts/init_day.sh b/scripts/init_day.sh new file mode 100755 index 0000000..2e347b7 --- /dev/null +++ b/scripts/init_day.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +AOC_DAY="$1" + +[[ -z "${AOC_DAY}" ]] && echo "Error: AoC day must be passed as first argument" && exit 10 + +cat <> "src/main/scala/Day${AOC_DAY}.scala" +import cats.derived.* +import cats.syntax.all.* + +object Day${AOC_DAY}: + + def day${AOC_DAY}: Int = 42 +EOT + +touch "src/test/scala/day${AOC_DAY}_input.txt" + +cat <> "src/test/scala/Day${AOC_DAY}Suite.scala" +import Day${AOC_DAY}.* +import Day${AOC_DAY}Suite.* +import munit.ScalaCheckSuite +import org.scalacheck.Gen +import org.scalacheck.Prop.* + +class Day${AOC_DAY}Suite extends ScalaCheckSuite: + + test("day${AOC_DAY} == 42"): + assertEquals(day${AOC_DAY}, 42) + +object Day${AOC_DAY}Suite: + + val bigInput: List[String] = getLinesFromFile("src/test/scala/day${AOC_DAY}_input.txt") +EOT diff --git a/src/main/scala/Day01.scala b/src/main/scala/Day01.scala new file mode 100644 index 0000000..7189d38 --- /dev/null +++ b/src/main/scala/Day01.scala @@ -0,0 +1,6 @@ +import cats.derived.* +import cats.syntax.all.* + +object Day01: + + def day01: Int = 42 diff --git a/src/test/scala/Day01Suite.scala b/src/test/scala/Day01Suite.scala new file mode 100644 index 0000000..26bdca0 --- /dev/null +++ b/src/test/scala/Day01Suite.scala @@ -0,0 +1,14 @@ +import munit.ScalaCheckSuite +import org.scalacheck.Gen +import org.scalacheck.Prop.* +import Day01.* +import Day01Suite.* + +class Day01Suite extends ScalaCheckSuite: + + test("day01 == 42"): + assertEquals(day01, 42) + +object Day01Suite: + + val bigInput: List[String] = getLinesFromFile("src/test/scala/day01_input.txt") diff --git a/src/test/scala/FileLoader.scala b/src/test/scala/FileLoader.scala new file mode 100644 index 0000000..e95cc5b --- /dev/null +++ b/src/test/scala/FileLoader.scala @@ -0,0 +1,7 @@ +import scala.io.Source + +def getLinesFromFile(filename: String): List[String] = + val source = Source.fromFile(filename) + val result = source.getLines().toList + source.close + result diff --git a/src/test/scala/day01_input.txt b/src/test/scala/day01_input.txt new file mode 100644 index 0000000..e69de29