From 9eedc93fb29846851863c8534f08b22f04ccb9b9 Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Wed, 3 Jul 2024 13:17:31 -0600 Subject: [PATCH] Start github-workflow-commands library (#1) --- .github/dependabot.yml | 6 + .github/workflows/ci.yml | 49 +++ .github/workflows/release.yml | 22 + .gitignore | 5 + .restyled.yaml | 10 + CHANGELOG.md | 3 + LICENSE | 21 + README.md | 11 + flake.lock | 375 ++++++++++++++++++ flake.nix | 44 ++ fourmolu.yaml | 15 + github-workflow-commands.cabal | 112 ++++++ library/GitHub/Workflow/Command/Syntax.hs | 30 ++ .../GitHub/Workflow/Command/Syntax/Command.hs | 56 +++ library/GitHub/Workflow/Command/Syntax/Key.hs | 21 + .../GitHub/Workflow/Command/Syntax/Message.hs | 38 ++ .../GitHub/Workflow/Command/Syntax/Name.hs | 25 ++ .../Workflow/Command/Syntax/Properties.hs | 52 +++ .../GitHub/Workflow/Command/Syntax/TextIso.hs | 9 + .../Command/Syntax/ToByteStringBuilder.hs | 15 + .../GitHub/Workflow/Command/Syntax/Value.hs | 33 ++ package.yaml | 80 ++++ stack.yaml | 1 + stack.yaml.lock | 12 + tests/GitHub/Workflow/Command/SyntaxSpec.hs | 42 ++ tests/Spec.hs | 1 + tests/SpecHook.hs | 11 + weeder.toml | 2 + 28 files changed, 1101 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .restyled.yaml create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 fourmolu.yaml create mode 100644 github-workflow-commands.cabal create mode 100644 library/GitHub/Workflow/Command/Syntax.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/Command.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/Key.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/Message.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/Name.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/Properties.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/TextIso.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/ToByteStringBuilder.hs create mode 100644 library/GitHub/Workflow/Command/Syntax/Value.hs create mode 100644 package.yaml create mode 100644 stack.yaml create mode 100644 stack.yaml.lock create mode 100644 tests/GitHub/Workflow/Command/SyntaxSpec.hs create mode 100644 tests/Spec.hs create mode 100644 tests/SpecHook.hs create mode 100644 weeder.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1230149 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..72b9d18 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI + +on: + pull_request: + push: + branches: main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - id: generate + uses: freckle/stack-action/generate-matrix@v4 + outputs: + stack-yamls: ${{ steps.generate.outputs.stack-yamls }} + + test: + needs: generate + runs-on: ubuntu-latest + + strategy: + matrix: + stack-yaml: ${{ fromJSON(needs.generate.outputs.stack-yamls) }} + fail-fast: false + + steps: + - uses: actions/checkout@v4 + - id: stack + uses: freckle/stack-action@v4 + with: + stack-yaml: ${{ matrix.stack-yaml }} + - if: ${{ matrix.stack-yaml == 'stack.yaml' }} + uses: freckle/weeder-action@v2 + with: + ghc-version: ${{ steps.stack.outputs.compiler-version }} + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: haskell/actions/hlint-setup@v2 + - uses: haskell/actions/hlint-run@v2 + with: + fail-on: warning diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c346083 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Release + +on: + push: + branches: main + +jobs: + release: + if: false # Remove when ready to release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - id: tag + uses: freckle/haskell-tag-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - if: steps.tag.outputs.tag + uses: freckle/stack-upload-action@v2 + env: + HACKAGE_API_KEY: ${{ secrets.HACKAGE_UPLOAD_API_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ea6cda --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.stack-work +.stack-work* +*.hie +.hiedb +dist-newstyle diff --git a/.restyled.yaml b/.restyled.yaml new file mode 100644 index 0000000..effe48b --- /dev/null +++ b/.restyled.yaml @@ -0,0 +1,10 @@ +restylers: + - fourmolu: + image: restyled/restyler-fourmolu:v0.13.0.0 + include: + - "/**/*.hs" + +request_review: author + +labels: + - restyled diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7fa715d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## [_Unreleased_](https://github.com/freckle/github-workflow-commands/compare/v__...main) + +- Unreleased features diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5788a64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 Renaissance Learning Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cfdbb3f --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# github-workflow-commands + +For printing workflow commands in GitHub Actions. + +See [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions). + +The code herein is based on [GitHub Actions Toolkit](https://github.com/actions/toolkit/tree/main/packages/core/src). + +--- + +[CHANGELOG](./CHANGELOG.md) | [LICENSE](./LICENSE) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7ffc067 --- /dev/null +++ b/flake.lock @@ -0,0 +1,375 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "freckle": { + "inputs": { + "flake-utils": "flake-utils_2", + "nix-github-actions": "nix-github-actions", + "nixpkgs-22-11": "nixpkgs-22-11", + "nixpkgs-23-05": "nixpkgs-23-05", + "nixpkgs-23-11": "nixpkgs-23-11", + "nixpkgs-haskell-updates": "nixpkgs-haskell-updates", + "nixpkgs-master-2023-05-06": "nixpkgs-master-2023-05-06", + "nixpkgs-master-2023-07-18": "nixpkgs-master-2023-07-18", + "nixpkgs-master-2023-09-15": "nixpkgs-master-2023-09-15", + "nixpkgs-master-2024-01-27": "nixpkgs-master-2024-01-27", + "nixpkgs-stable": "nixpkgs-stable", + "nixpkgs-stable-2023-07-25": "nixpkgs-stable-2023-07-25", + "nixpkgs-unstable-2023-10-21": "nixpkgs-unstable-2023-10-21", + "nixpkgs-unstable-2024-02-20": "nixpkgs-unstable-2024-02-20", + "nixpkgs-unstable-2024-04-03": "nixpkgs-unstable-2024-04-03", + "nixpkgs-unstable-2024-05-30": "nixpkgs-unstable-2024-05-30" + }, + "locked": { + "dir": "main", + "lastModified": 1719604962, + "narHash": "sha256-Nw8KbsdjgOp1IRFm7YYd2sLR2KY0NX/lNRxq+lcbzEg=", + "ref": "refs/heads/main", + "rev": "ffa94bbc1d26ef8ba498fbe6ad4a9bdb2db82337", + "revCount": 46, + "type": "git", + "url": "ssh://git@github.com/freckle/flakes?dir=main" + }, + "original": { + "dir": "main", + "type": "git", + "url": "ssh://git@github.com/freckle/flakes?dir=main" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "freckle", + "nixpkgs-stable" + ] + }, + "locked": { + "lastModified": 1703863825, + "narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "5163432afc817cf8bd1f031418d1869e4c9d5547", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nixpkgs-22-11": { + "locked": { + "lastModified": 1688392541, + "narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-23-05": { + "locked": { + "lastModified": 1704290814, + "narHash": "sha256-LWvKHp7kGxk/GEtlrGYV68qIvPHkU9iToomNFGagixU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "70bdadeb94ffc8806c0570eb5c2695ad29f0e421", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-23-11": { + "locked": { + "lastModified": 1717530100, + "narHash": "sha256-b4Dn+PnrZoVZ/BoR9JN2fTxXxplJrAsdSUIePf4Cacs=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "a2e1d0414259a144ebdc048408a807e69e0565af", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-haskell-updates": { + "locked": { + "lastModified": 1717667911, + "narHash": "sha256-naJoCoUil31xjrUixCsKFogTkTiI02fv2X/6QukpacA=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "f166c7778ccf61d7f8b89a9a94060dce070458d0", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "haskell-updates", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-master-2023-05-06": { + "locked": { + "lastModified": 1683392273, + "narHash": "sha256-pZTuxvcuDeBG+vvE1zczNyEUzlPbzXVh8Ed45Fzo+tQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "16b3b0c53b1ee8936739f8c588544e7fcec3fc60", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "16b3b0c53b1ee8936739f8c588544e7fcec3fc60", + "type": "github" + } + }, + "nixpkgs-master-2023-07-18": { + "locked": { + "lastModified": 1689680872, + "narHash": "sha256-brNix2+ihJSzCiKwLafbyejrHJZUP0Fy6z5+xMOC27M=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "08700de174bc6235043cb4263b643b721d936bdb", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "08700de174bc6235043cb4263b643b721d936bdb", + "type": "github" + } + }, + "nixpkgs-master-2023-09-15": { + "locked": { + "lastModified": 1694760568, + "narHash": "sha256-3G07BiXrp2YQKxdcdms22MUx6spc6A++MSePtatCYuI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "46688f8eb5cd6f1298d873d4d2b9cf245e09e88e", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "46688f8eb5cd6f1298d873d4d2b9cf245e09e88e", + "type": "github" + } + }, + "nixpkgs-master-2024-01-27": { + "locked": { + "lastModified": 1706367331, + "narHash": "sha256-AqgkGHRrI6h/8FWuVbnkfFmXr4Bqsr4fV23aISqj/xg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "160b762eda6d139ac10ae081f8f78d640dd523eb", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "160b762eda6d139ac10ae081f8f78d640dd523eb", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1717555607, + "narHash": "sha256-WZ1s48OODmRJ3DHC+I/DtM3tDRuRJlNqMvxvAPTD7ec=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "0b8e7a1ae5a94da2e1ee3f3030a32020f6254105", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable-2023-07-25": { + "locked": { + "lastModified": 1690271650, + "narHash": "sha256-qwdsW8DBY1qH+9luliIH7VzgwvL+ZGI3LZWC0LTiDMI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "6dc93f0daec55ee2f441da385aaf143863e3d671", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "6dc93f0daec55ee2f441da385aaf143863e3d671", + "type": "github" + } + }, + "nixpkgs-unstable-2023-10-21": { + "locked": { + "lastModified": 1697793076, + "narHash": "sha256-02e7sCuqLtkyRgrZmdOyvAcQTQdcXj+vpyp9bca6cY4=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "038b2922be3fc096e1d456f93f7d0f4090628729", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "038b2922be3fc096e1d456f93f7d0f4090628729", + "type": "github" + } + }, + "nixpkgs-unstable-2024-02-20": { + "locked": { + "lastModified": 1708296515, + "narHash": "sha256-FyF489fYNAUy7b6dkYV6rGPyzp+4tThhr80KNAaF/yY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa", + "type": "github" + } + }, + "nixpkgs-unstable-2024-04-03": { + "locked": { + "lastModified": 1712143413, + "narHash": "sha256-kI6WYXuj8/2AtBF4o0gYnkYNO1xtc6PaPbW/qmaV67A=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "62e885a4013446453b10fd7780eba4337f6f42e0", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "62e885a4013446453b10fd7780eba4337f6f42e0", + "type": "github" + } + }, + "nixpkgs-unstable-2024-05-30": { + "locked": { + "lastModified": 1717070961, + "narHash": "sha256-0JN98HVWPMlxj48Ot9K3eF0CWfwvo7/7VAYO+VAHg1Q=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "aa61b27554a5fc282758bf0324781e3464ef2cde", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "aa61b27554a5fc282758bf0324781e3464ef2cde", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "freckle": "freckle", + "stable": "stable" + } + }, + "stable": { + "locked": { + "lastModified": 1719838683, + "narHash": "sha256-Zw9rQjHz1ilNIimEXFeVa1ERNRBF8DoXDhLAZq5B4pE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "d032c1a6dfad4eedec7e35e91986becc699d7d69", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..09010f2 --- /dev/null +++ b/flake.nix @@ -0,0 +1,44 @@ +{ + inputs = { + stable.url = "github:nixos/nixpkgs/nixos-24.05"; + freckle.url = "git+ssh://git@github.com/freckle/flakes?dir=main"; + flake-utils.url = "github:numtide/flake-utils"; + }; + outputs = inputs: + inputs.flake-utils.lib.eachDefaultSystem (system: + let + nixpkgsArgs = { inherit system; config = { }; }; + + nixpkgs = { + stable = import inputs.stable nixpkgsArgs; + }; + freckle = inputs.freckle.packages.${system}; + freckleLib = inputs.freckle.lib.${system}; + + in + rec { + packages = { + fourmolu = freckle.fourmolu-0-13-x; + + ghc = freckleLib.haskellBundle { + ghcVersion = "ghc-9-6-5"; + enableHLS = true; + }; + }; + + devShells.default = nixpkgs.stable.mkShell { + buildInputs = with (nixpkgs.stable); [ + zlib + ]; + + nativeBuildInputs = with (packages); [ + fourmolu + ghc + ]; + + shellHook = '' + export STACK_YAML=stack.yaml + ''; + }; + }); +} diff --git a/fourmolu.yaml b/fourmolu.yaml new file mode 100644 index 0000000..ef571e8 --- /dev/null +++ b/fourmolu.yaml @@ -0,0 +1,15 @@ +indentation: 2 +column-limit: 80 # ignored until v12 / ghc-9.6 +function-arrows: leading +comma-style: leading # default +import-export-style: leading +indent-wheres: false # default +record-brace-space: true +newlines-between-decls: 1 # default +haddock-style: single-line +let-style: mixed +in-style: left-align +single-constraint-parens: never # ignored until v12 / ghc-9.6 +unicode: never # default +respectful: true # default +fixities: [] # default diff --git a/github-workflow-commands.cabal b/github-workflow-commands.cabal new file mode 100644 index 0000000..9c3a370 --- /dev/null +++ b/github-workflow-commands.cabal @@ -0,0 +1,112 @@ +cabal-version: 1.18 + +-- This file has been generated from package.yaml by hpack version 0.36.0. +-- +-- see: https://github.com/sol/hpack + +name: github-workflow-commands +version: 0.0.0.0 +synopsis: GitHub Actions workflow commands +description: For printing workflow commands in GitHub Actions. + . + See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions +category: Utils +homepage: https://github.com/freckle/github-workflow-commands#readme +bug-reports: https://github.com/freckle/github-workflow-commands/issues +maintainer: Freckle Education +license: MIT +license-file: LICENSE +build-type: Simple +extra-source-files: + package.yaml +extra-doc-files: + README.md + CHANGELOG.md + +source-repository head + type: git + location: https://github.com/freckle/github-workflow-commands + +library + exposed-modules: + GitHub.Workflow.Command.Syntax + GitHub.Workflow.Command.Syntax.Command + GitHub.Workflow.Command.Syntax.Key + GitHub.Workflow.Command.Syntax.Message + GitHub.Workflow.Command.Syntax.Name + GitHub.Workflow.Command.Syntax.Properties + GitHub.Workflow.Command.Syntax.TextIso + GitHub.Workflow.Command.Syntax.ToByteStringBuilder + GitHub.Workflow.Command.Syntax.Value + other-modules: + Paths_github_workflow_commands + hs-source-dirs: + library + default-extensions: + BlockArguments + DataKinds + DeriveAnyClass + DerivingStrategies + DerivingVia + DuplicateRecordFields + GADTs + LambdaCase + NoFieldSelectors + NoImplicitPrelude + NoMonomorphismRestriction + NoPostfixOperators + OverloadedRecordDot + OverloadedStrings + QuasiQuotes + TypeFamilies + ghc-options: -fwrite-ide-info -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe + build-depends: + base <5 + , bytestring + , containers + , lens + , text + default-language: GHC2021 + if impl(ghc >= 9.2) + ghc-options: -Wno-missing-kind-signatures + if impl(ghc >= 8.10) + ghc-options: -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module + +test-suite spec + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + GitHub.Workflow.Command.SyntaxSpec + SpecHook + Paths_github_workflow_commands + hs-source-dirs: + tests + default-extensions: + BlockArguments + DataKinds + DeriveAnyClass + DerivingStrategies + DerivingVia + DuplicateRecordFields + GADTs + LambdaCase + NoFieldSelectors + NoImplicitPrelude + NoMonomorphismRestriction + NoPostfixOperators + OverloadedRecordDot + OverloadedStrings + QuasiQuotes + TypeFamilies + ghc-options: -fwrite-ide-info -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe -threaded -rtsopts "-with-rtsopts=-N" + build-depends: + base <5 + , github-workflow-commands + , hspec + , hspec-junit-formatter + , lens + default-language: GHC2021 + if impl(ghc >= 9.2) + ghc-options: -Wno-missing-kind-signatures + if impl(ghc >= 8.10) + ghc-options: -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module diff --git a/library/GitHub/Workflow/Command/Syntax.hs b/library/GitHub/Workflow/Command/Syntax.hs new file mode 100644 index 0000000..1a153bf --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax.hs @@ -0,0 +1,30 @@ +module GitHub.Workflow.Command.Syntax + ( -- * Types + Command + , Name + , Properties + , Key + , Value + , Message + + -- * Working with commands + , command + , property + , TextIso (..) + , HasMessage (..) + , HasProperties (..) + + -- * Printing commands + , ToByteStringBuilder + , toByteString + , toByteStringBuilder + ) where + +import GitHub.Workflow.Command.Syntax.Command +import GitHub.Workflow.Command.Syntax.Key +import GitHub.Workflow.Command.Syntax.Message +import GitHub.Workflow.Command.Syntax.Name +import GitHub.Workflow.Command.Syntax.Properties +import GitHub.Workflow.Command.Syntax.TextIso +import GitHub.Workflow.Command.Syntax.ToByteStringBuilder +import GitHub.Workflow.Command.Syntax.Value diff --git a/library/GitHub/Workflow/Command/Syntax/Command.hs b/library/GitHub/Workflow/Command/Syntax/Command.hs new file mode 100644 index 0000000..c571fed --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/Command.hs @@ -0,0 +1,56 @@ +module GitHub.Workflow.Command.Syntax.Command + ( Command + , command + ) where + +import Control.Category +import Control.Lens (lens) +import Control.Monad (mfilter) +import Data.Foldable (foldMap) +import Data.Maybe (Maybe) +import Data.String (IsString (fromString)) +import GitHub.Workflow.Command.Syntax.Message +import GitHub.Workflow.Command.Syntax.Name +import GitHub.Workflow.Command.Syntax.Properties +import GitHub.Workflow.Command.Syntax.Properties qualified as Properties +import GitHub.Workflow.Command.Syntax.ToByteStringBuilder +import Prelude (Eq, Maybe (..), Ord, Show, not, (<>)) + +data Command = Command + { name :: Name + , properties :: Properties + , message :: Message + } + deriving stock (Eq, Ord, Show) + +instance IsString Command where + fromString = command . fromString + +instance HasMessage Command where + message = lens (.message) \x y -> x {message = y} + +instance HasProperties Command where + properties = lens (.properties) \x y -> x {properties = y} + +-- | Construct a minimal command with a command 'Name' e.g. "warning" or "error" +-- +-- See the 'GitHub.Workflow.Command.Syntax.Properties.property' and +-- 'GitHub.Workflow.Command.Syntax.Message.message' lenses for other +-- information include in a command. +command :: Name -> Command +command x = + Command + { name = x + , properties = Properties.empty + , message = "" + } + +instance ToByteStringBuilder Command where + toByteStringBuilder x = + "::" + <> toByteStringBuilder x.name + <> foldMap @Maybe + (\p -> " " <> toByteStringBuilder p) + (mfilter (not . Properties.null) (Just x.properties)) + <> "::" + <> toByteStringBuilder x.message diff --git a/library/GitHub/Workflow/Command/Syntax/Key.hs b/library/GitHub/Workflow/Command/Syntax/Key.hs new file mode 100644 index 0000000..80c519d --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/Key.hs @@ -0,0 +1,21 @@ +module GitHub.Workflow.Command.Syntax.Key + ( Key + ) where + +import Control.Category +import Control.Lens (iso) +import Data.String (IsString) +import Data.Text (Text) +import Data.Text.Encoding qualified as T +import GitHub.Workflow.Command.Syntax.TextIso +import GitHub.Workflow.Command.Syntax.ToByteStringBuilder +import Prelude (Eq, Ord, Show) + +newtype Key = Key {text :: Text} + deriving newtype (Eq, Ord, Show, IsString) + +instance ToByteStringBuilder Key where + toByteStringBuilder = T.encodeUtf8Builder . (.text) + +instance TextIso Key where + text = iso (.text) Key diff --git a/library/GitHub/Workflow/Command/Syntax/Message.hs b/library/GitHub/Workflow/Command/Syntax/Message.hs new file mode 100644 index 0000000..4e701cd --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/Message.hs @@ -0,0 +1,38 @@ +module GitHub.Workflow.Command.Syntax.Message + ( Message + , HasMessage (..) + ) where + +import Control.Category +import Control.Lens (Lens', iso, simple) +import Data.String (IsString) +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import GitHub.Workflow.Command.Syntax.TextIso +import GitHub.Workflow.Command.Syntax.ToByteStringBuilder +import Prelude (Eq, Ord, Show) + +newtype Message = Message {text :: Text} + deriving newtype (Eq, Ord, Show, IsString) + +instance ToByteStringBuilder Message where + toByteStringBuilder = + T.encodeUtf8Builder + . T.concatMap + ( \case + '%' -> "%25" + '\r' -> "%0D" + '\n' -> "%0A" + x -> T.singleton x + ) + . (.text) + +instance TextIso Message where + text = iso (.text) Message + +class HasMessage a where + message :: Lens' a Message + +instance HasMessage Message where + message = simple diff --git a/library/GitHub/Workflow/Command/Syntax/Name.hs b/library/GitHub/Workflow/Command/Syntax/Name.hs new file mode 100644 index 0000000..1e17892 --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/Name.hs @@ -0,0 +1,25 @@ +module GitHub.Workflow.Command.Syntax.Name + ( Name + ) where + +import Control.Category +import Control.Lens (iso) +import Data.String (IsString) +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import GitHub.Workflow.Command.Syntax.TextIso +import GitHub.Workflow.Command.Syntax.ToByteStringBuilder +import Prelude (Eq, Ord, Show) + +newtype Name = Name {text :: Text} + deriving newtype (Eq, Ord, Show, IsString) + +instance ToByteStringBuilder Name where + toByteStringBuilder = + T.encodeUtf8Builder + . (\x -> if T.null x then "missing.command" else x) + . (.text) + +instance TextIso Name where + text = iso (.text) Name diff --git a/library/GitHub/Workflow/Command/Syntax/Properties.hs b/library/GitHub/Workflow/Command/Syntax/Properties.hs new file mode 100644 index 0000000..70426b4 --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/Properties.hs @@ -0,0 +1,52 @@ +module GitHub.Workflow.Command.Syntax.Properties + ( Properties + , HasProperties (..) + , property + , empty + , null + ) where + +import Control.Category +import Control.Lens (Lens', at, iso, simple) +import Data.Foldable (fold) +import Data.Functor +import Data.List qualified as List +import Data.Map.Strict (Map) +import Data.Map.Strict qualified as Map +import Data.Maybe (Maybe (..)) +import Data.Semigroup +import GitHub.Workflow.Command.Syntax.Key (Key) +import GitHub.Workflow.Command.Syntax.ToByteStringBuilder +import GitHub.Workflow.Command.Syntax.Value (Value) +import Prelude (Bool, Eq, Ord, Show) + +newtype Properties = Properties {map :: Map Key Value} + deriving stock (Eq, Ord, Show) + +empty :: Properties +empty = Properties Map.empty + +null :: Properties -> Bool +null = Map.null . (.map) + +instance ToByteStringBuilder Properties where + toByteStringBuilder = + fold + . List.intersperse "," + . fmap + ( \(key, value) -> + toByteStringBuilder key + <> "=" + <> toByteStringBuilder value + ) + . Map.toAscList + . (.map) + +class HasProperties a where + properties :: Lens' a Properties + +instance HasProperties Properties where + properties = simple + +property :: HasProperties a => Key -> Lens' a (Maybe Value) +property k = properties . iso (.map) Properties . at k diff --git a/library/GitHub/Workflow/Command/Syntax/TextIso.hs b/library/GitHub/Workflow/Command/Syntax/TextIso.hs new file mode 100644 index 0000000..16e153f --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/TextIso.hs @@ -0,0 +1,9 @@ +module GitHub.Workflow.Command.Syntax.TextIso + ( TextIso (..) + ) where + +import Control.Lens (Iso') +import Data.Text (Text) + +class TextIso a where + text :: Iso' a Text diff --git a/library/GitHub/Workflow/Command/Syntax/ToByteStringBuilder.hs b/library/GitHub/Workflow/Command/Syntax/ToByteStringBuilder.hs new file mode 100644 index 0000000..d93f9d7 --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/ToByteStringBuilder.hs @@ -0,0 +1,15 @@ +module GitHub.Workflow.Command.Syntax.ToByteStringBuilder + ( ToByteStringBuilder (..) + , toByteString + ) where + +import Control.Category +import Data.ByteString +import Data.ByteString.Builder qualified as BSB +import Data.ByteString.Lazy qualified as BSL + +class ToByteStringBuilder a where + toByteStringBuilder :: a -> BSB.Builder + +toByteString :: ToByteStringBuilder a => a -> ByteString +toByteString = BSL.toStrict . BSB.toLazyByteString . toByteStringBuilder diff --git a/library/GitHub/Workflow/Command/Syntax/Value.hs b/library/GitHub/Workflow/Command/Syntax/Value.hs new file mode 100644 index 0000000..6be5693 --- /dev/null +++ b/library/GitHub/Workflow/Command/Syntax/Value.hs @@ -0,0 +1,33 @@ +module GitHub.Workflow.Command.Syntax.Value + ( Value + ) where + +import Control.Category +import Control.Lens (iso) +import Data.String (IsString) +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import GitHub.Workflow.Command.Syntax.TextIso +import GitHub.Workflow.Command.Syntax.ToByteStringBuilder +import Prelude (Eq, Ord, Show) + +newtype Value = Value {text :: Text} + deriving newtype (Eq, Ord, Show, IsString) + +instance ToByteStringBuilder Value where + toByteStringBuilder = + T.encodeUtf8Builder + . T.concatMap + ( \case + '%' -> "%25" + '\r' -> "%0D" + '\n' -> "%0A" + ':' -> "%3A" + ',' -> "%2C" + x -> T.singleton x + ) + . (.text) + +instance TextIso Value where + text = iso (.text) Value diff --git a/package.yaml b/package.yaml new file mode 100644 index 0000000..f071bd3 --- /dev/null +++ b/package.yaml @@ -0,0 +1,80 @@ +name: github-workflow-commands +version: 0.0.0.0 +maintainer: Freckle Education +category: Utils +github: freckle/github-workflow-commands +synopsis: GitHub Actions workflow commands +description: | + For printing workflow commands in GitHub Actions. + + See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions + +extra-doc-files: + - README.md + - CHANGELOG.md + +extra-source-files: + - package.yaml + +ghc-options: + - -fwrite-ide-info + - -Weverything + - -Wno-all-missed-specialisations + - -Wno-missed-specialisations + - -Wno-missing-exported-signatures + - -Wno-missing-import-lists + - -Wno-missing-local-signatures + - -Wno-monomorphism-restriction + - -Wno-safe + - -Wno-unsafe + +when: + - condition: "impl(ghc >= 9.2)" + ghc-options: + - -Wno-missing-kind-signatures + - condition: "impl(ghc >= 8.10)" + ghc-options: + - -Wno-missing-safe-haskell-mode + - -Wno-prepositive-qualified-module + +dependencies: + - base < 5 + +language: GHC2021 + +default-extensions: + - BlockArguments + - DataKinds + - DeriveAnyClass + - DerivingStrategies + - DerivingVia + - DuplicateRecordFields + - GADTs + - LambdaCase + - NoFieldSelectors + - NoImplicitPrelude + - NoMonomorphismRestriction + - NoPostfixOperators + - OverloadedRecordDot + - OverloadedStrings + - QuasiQuotes + - TypeFamilies + +library: + source-dirs: library + dependencies: + - bytestring + - containers + - lens + - text + +tests: + spec: + main: Spec.hs + source-dirs: tests + ghc-options: -threaded -rtsopts "-with-rtsopts=-N" + dependencies: + - github-workflow-commands + - hspec + - hspec-junit-formatter + - lens diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..5ed2ee4 --- /dev/null +++ b/stack.yaml @@ -0,0 +1 @@ +resolver: lts-22.27 diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..c2b206b --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + sha256: bc144ddf301a5c99f2cf51c7de50279ba144fd4486cb3c66f87ed761d6bbf6e9 + size: 719131 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/27.yaml + original: lts-22.27 diff --git a/tests/GitHub/Workflow/Command/SyntaxSpec.hs b/tests/GitHub/Workflow/Command/SyntaxSpec.hs new file mode 100644 index 0000000..8aeccf9 --- /dev/null +++ b/tests/GitHub/Workflow/Command/SyntaxSpec.hs @@ -0,0 +1,42 @@ +module GitHub.Workflow.Command.SyntaxSpec + ( spec + ) where + +import Control.Lens +import Data.Function (($)) +import Data.Maybe (Maybe (..)) +import GitHub.Workflow.Command.Syntax +import Test.Hspec + +spec :: Spec +spec = + context "Command to ByteString" do + specify "empty command name" $ + shouldBe + ( toByteString $ command "" + ) + "::missing.command::" + + specify "endgroup" $ + shouldBe + ( toByteString $ command "endgroup" + ) + "::endgroup::" + + specify "error" $ + shouldBe + ( toByteString $ + command "error" + & set message "Missing semicolon" + & set (property "file") (Just "app.js") + & set (property "line") (Just "1") + ) + "::error file=app.js,line=1::Missing semicolon" + + specify "debug" $ + shouldBe + ( toByteString $ + command "debug" + & set message "Set the Octocat variable" + ) + "::debug::Set the Octocat variable" diff --git a/tests/Spec.hs b/tests/Spec.hs new file mode 100644 index 0000000..a824f8c --- /dev/null +++ b/tests/Spec.hs @@ -0,0 +1 @@ +{-# OPTIONS_GHC -F -pgmF hspec-discover #-} diff --git a/tests/SpecHook.hs b/tests/SpecHook.hs new file mode 100644 index 0000000..e064a69 --- /dev/null +++ b/tests/SpecHook.hs @@ -0,0 +1,11 @@ +module SpecHook + ( hook + ) where + +import Prelude + +import Test.Hspec +import Test.Hspec.JUnit.Formatter.Env as Formatter + +hook :: Spec -> Spec +hook = Formatter.whenEnabled Formatter.add . parallel diff --git a/weeder.toml b/weeder.toml new file mode 100644 index 0000000..58d206f --- /dev/null +++ b/weeder.toml @@ -0,0 +1,2 @@ +roots = ["Main.main", "^Paths_.*"] +type-class-roots = true