diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4724ade..2463fb9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,6 +7,7 @@ on: branches: [master] jobs: checks: + name: "Run Nix checks" runs-on: ${{ matrix.os }} strategy: matrix: @@ -25,5 +26,97 @@ jobs: - name: Run Nix checks run: nix flake check - - name: Build with Nix (runs tests) + default-ghc: + name: "Build & test with Nix (default unstable GHC)" + needs: [checks] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v27 + + - name: Enable cache for Nix + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Build default package with Nix run: nix build + + ghc-94: + name: "Build & test with Nix (GHC 9.4)" + needs: [checks] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v27 + + - name: Enable cache for Nix + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Build package with Nix (GHC 9.4) + run: nix build ".#richenv-ghc94" + + ghc-96: + name: "Build & test with Nix (GHC 9.6)" + needs: [checks] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v27 + + - name: Enable cache for Nix + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Build package with Nix (GHC 9.6) + run: nix build ".#richenv-ghc96" + + ghc-98: + name: "Build & test with Nix (GHC 9.8)" + needs: [checks] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v27 + + - name: Enable cache for Nix + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Build package with Nix (GHC 9.8) + run: nix build ".#richenv-ghc98" + + # ghc-910: + # name: "Build & test with Nix (GHC 9.10)" + # needs: [checks] + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [ubuntu-latest, macos-latest] + # steps: + # - uses: actions/checkout@v4 + + # - name: Install Nix + # uses: cachix/install-nix-action@v27 + + # - name: Enable cache for Nix + # uses: DeterminateSystems/magic-nix-cache-action@v2 + + # - name: Build package with Nix (GHC 9.10) + # run: nix build ".#richenv-ghc910" diff --git a/.hlint.yaml b/.hlint.yaml new file mode 100644 index 0000000..827ecb9 --- /dev/null +++ b/.hlint.yaml @@ -0,0 +1,134 @@ +# Haskell's Dangerous Functions +# For a how-to-use and the latest version of this file go to: +# https://github.com/NorfairKing/haskell-dangerous-functions/ + +- ignore: { name: "Use unless" } +- ignore: { name: "Use tuple-section" } + +- functions: + - {name: unsafeDupablePerformIO, within: []} # Unsafe + - {name: unsafeInterleaveIO, within: []} # Unsafe + - {name: unsafeFixIO, within: []} # Unsafe + - {name: unsafePerformIO, within: []} # Unsafe + + # _VERY_ hard to get right, use the async library instead. + # See also https://github.com/informatikr/hedis/issues/165 + - {name: forkIO, within: []} + # Mostly impossible to get right, rethink what you're doing entirely. + # See also https://www.reddit.com/r/haskell/comments/jsap9r/how_dangerous_is_forkprocess/ + - {name: forkProcess, within: []} + + - {name: undefined, within: []} # Purposely fails. Deal with errors appropriately instead. + - {name: throw, within: []} # Don't throw from pure code, use throwIO instead. + - {name: Prelude.error, within: []} + + - {name: Data.List.head, within: []} # Partial, use `listToMaybe` instead. + - {name: Data.List.tail, within: []} # Partial + - {name: Data.List.init, within: []} # Partial + - {name: Data.List.last, within: []} # Partial + - {name: 'Data.List.!!', within: []} # Partial + - {name: Data.List.genericIndex, within: []} # Partial + - {name: Data.List.genericLength, within: []} + + # Same, but for Data.Text + - {name: Data.Text.head, within: []} + - {name: Data.Text.tail, within: []} + - {name: Data.Text.init, within: []} + - {name: Data.Text.last, within: []} + + - {name: minimum, within: []} # Partial + - {name: minimumBy, within: []} # Partial + - {name: maximum, within: []} # Partial + - {name: maximumBy, within: []} # Partial + + # Same, but for Data.Text + - {name: Data.Text.maximum, within: []} + - {name: Data.Text.minimum, within: []} + + - {name: GHC.Enum.pred, within: []} # Partial + - {name: GHC.Enum.succ, within: []} # Partial + - {name: GHC.Enum.toEnum, within: []} # Partial + - {name: GHC.Enum.fromEnum, within: []} # Does not do what you think it does. + - {name: GHC.Enum.enumFrom, within: []} # Does not do what you think it does, depending on the type. + - {name: GHC.Enum.enumFromThen, within: []} # Does not do what you think it does, depending on the type. + - {name: GHC.Enum.enumFromTo, within: []} # Does not do what you think it does, depending on the type. + - {name: GHC.Enum.enumFromThenTo, within: []} # Does not do what you think it does, depending on the type. + + - {name: unless, within: []} # Really confusing, use 'when' instead. + - {name: either, within: []} # Really confusing, just use a case-match. + + - {name: nub, within: []} # O(n^2) + + - {name: Data.Foldable.foldl, within: []} # Lazy accumulator. Use foldl' instead. + - {name: Data.Foldable.foldMap, within: []} # Lazy accumulator. Use foldMap' instead. + - {name: Data.Foldable.sum, within: []} # Lazy accumulator + - {name: Data.Foldable.product, within: []} # Lazy accumulator + + # Functions involving division + - {name: Prelude.quot, within: []} # Partial, see https://github.com/NorfairKing/haskell-WAT#num-int + - {name: Prelude.div, within: []} + - {name: Prelude.rem, within: []} + - {name: Prelude.mod, within: []} + - {name: Prelude.quotRem, within: []} + - {name: Prelude.divMod, within: []} + + # Does unexpected things, see + # https://github.com/NorfairKing/haskell-WAT#real-double + - {name: realToFrac, within: []} + + # Constructs rationals, which is either wrong or a bad idea. + - {name: 'Data.Ratio.%', within: []} + + # Don't use string for command-line output. + - {name: System.IO.putChar, within: []} + - {name: System.IO.putStr, within: []} + - {name: System.IO.putStrLn, within: []} + - {name: System.IO.print, within: []} + + # Don't use string for command-line input either. + - {name: System.IO.getChar, within: []} + - {name: System.IO.getLine, within: []} + - {name: System.IO.getContents, within: []} # Does lazy IO. + - {name: System.IO.interact, within: []} + - {name: System.IO.readIO, within: []} + - {name: System.IO.readLn, within: []} + + # Don't use strings to interact with files + - {name: System.IO.readFile, within: []} + - {name: System.IO.writeFile, within: []} + - {name: System.IO.appendFile, within: []} + + # Can succeed in dev, but fail in prod, because of encoding guessing + # It's also Lazy IO. + # See https://www.snoyman.com/blog/2016/12/beware-of-readfile/ for more info. + - {name: Data.Text.IO.readFile, within: []} + - {name: Data.Text.IO.Lazy.readFile, within: []} + + - {name: Data.Text.Encoding.decodeUtf8, within: []} # Throws on invalid UTF8 + + - {name: fromJust, within: [], message: 'Partial'} # Partial + + # Does silent truncation: + # > fromIntegral (300 :: Word) :: Word8 + # 44 + - {name: fromIntegral, within: []} + - {name: fromInteger, within: []} + + + - {name: 'read', within: []} # Partial, use `Text.Read.readMaybe` instead. + + # Deprecated, use `pure` instead. + # See https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/monad-of-no-return + - {name: 'return', within: []} + +- modules: + - { name: Control.Lens, within: [] } + +- extensions: + - { name: DeriveAnyClass, within: [] } # Dangerous + + - { name: DuplicateRecordFields, within: [] } + + - { name: NamedFieldPuns, within: [] } + - { name: TupleSections, within: [] } + - { name: OverloadedLabels, within: [] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fd86d18..8952139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Revision history for richenv +## 0.1.0.2 -- 2024-05-20 + +* test: switch dependency for YAML parsing from `yaml` to `HsYAML`. + ## 0.1.0.1 -- 2023-09-25 * Fix changelog. diff --git a/default.nix b/default.nix index 0a05de4..5c9eefe 100644 --- a/default.nix +++ b/default.nix @@ -1,15 +1,15 @@ -{ mkDerivation, aeson, base, bytestring, hspec, hspec-discover, lib -, QuickCheck, quickcheck-instances, text, unordered-containers -, yaml +{ mkDerivation, aeson, base, bytestring, hspec, hspec-discover +, HsYAML, HsYAML-aeson, lib, QuickCheck, quickcheck-instances, text +, unordered-containers }: mkDerivation { pname = "richenv"; - version = "0.1.0.1"; + version = "0.1.0.2"; src = ./.; libraryHaskellDepends = [ aeson base text unordered-containers ]; testHaskellDepends = [ - aeson base bytestring hspec QuickCheck quickcheck-instances text - unordered-containers yaml + aeson base bytestring hspec HsYAML HsYAML-aeson QuickCheck + quickcheck-instances text unordered-containers ]; testToolDepends = [ hspec-discover ]; homepage = "https://github.com/DavSanchez/richenv"; diff --git a/flake.lock b/flake.lock index 6a11c20..e2588e8 100644 --- a/flake.lock +++ b/flake.lock @@ -21,11 +21,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1714641030, - "narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=", + "lastModified": 1715865404, + "narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e", + "rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9", "type": "github" }, "original": { @@ -34,38 +34,19 @@ "type": "github" } }, - "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" - } - }, "git-hooks": { "inputs": { "flake-compat": "flake-compat", - "flake-utils": "flake-utils", "gitignore": "gitignore", "nixpkgs": "nixpkgs", "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1715609711, - "narHash": "sha256-/5u29K0c+4jyQ8x7dUIEUWlz2BoTSZWUP2quPwFCE7M=", + "lastModified": 1716213921, + "narHash": "sha256-xrsYFST8ij4QWaV6HEokCUNIZLjjLP1bYC60K8XiBVA=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "c182c876690380f8d3b9557c4609472ebfa1b141", + "rev": "0e8fcc54b842ad8428c9e705cb5994eaf05c26a0", "type": "github" }, "original": { @@ -97,11 +78,11 @@ }, "haskell-flake": { "locked": { - "lastModified": 1715478199, - "narHash": "sha256-h4mSCAfCWxUP4rM0FxoKeFIqBJAhgh8wm22LRjn3rAg=", + "lastModified": 1716088826, + "narHash": "sha256-jLNcBHylm1J/Q1Vz7Swsao5J8lZAhQKKSUNHl+r1K7k=", "owner": "srid", "repo": "haskell-flake", - "rev": "988a78590c158c5fa0b4893de793c9c783b9d7e9", + "rev": "440b0f1d69c1b9bb1831c29b573cf3d2e50a2c9c", "type": "github" }, "original": { @@ -128,6 +109,7 @@ }, "nixpkgs-lib": { "locked": { + "lastModified": 1714640452, "narHash": "sha256-QBx10+k6JWz6u7VsohfSw8g8hjdBZEf8CFzXH1/1Z94=", "type": "tarball", "url": "https://github.com/NixOS/nixpkgs/archive/50eb7ecf4cd0a5756d7275c8ba36790e5bd53e33.tar.gz" @@ -155,11 +137,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1715653339, - "narHash": "sha256-7lR9tpVXviSccl07GXI0+ve/natd24HAkuy1sQp0OlI=", + "lastModified": 1716128955, + "narHash": "sha256-3DNg/PV+X2V7yn8b/fUR2ppakw7D9N4sjVBGk6nDwII=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "abd6d48f8c77bea7dc51beb2adfa6ed3950d2585", + "rev": "f9256de8281f2ccd04985ac5c30d8f69aefadbe8", "type": "github" }, "original": { @@ -176,21 +158,6 @@ "haskell-flake": "haskell-flake", "nixpkgs": "nixpkgs_2" } - }, - "systems": { - "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", diff --git a/flake.nix b/flake.nix index 8f55bed..05d98ef 100644 --- a/flake.nix +++ b/flake.nix @@ -10,8 +10,9 @@ outputs = inputs @ {flake-parts, ...}: flake-parts.lib.mkFlake {inherit inputs;} { - imports = [ - inputs.git-hooks.flakeModule + imports = with inputs; [ + git-hooks.flakeModule + # haskell-flake.flakeModule ]; systems = [ @@ -25,9 +26,7 @@ config, pkgs, ... - }: let - haskell = pkgs.haskellPackages; - in { + }: { pre-commit = { check.enable = true; settings = { @@ -43,8 +42,6 @@ ormolu.enable = true; hlint.enable = true; hpack.enable = true; - # yamllint.enable = true; - # hunspell.enable = true; }; }; }; @@ -56,7 +53,7 @@ ''; nativeBuildInputs = config.pre-commit.settings.enabledPackages; - buildInputs = with haskell; [ + buildInputs = with pkgs.haskellPackages; [ ghc cabal-install haskell-language-server @@ -64,7 +61,14 @@ ]; }; - packages.default = haskell.callPackage ./default.nix {}; + packages = { + default = pkgs.haskellPackages.callPackage ./default.nix {}; + + richenv-ghc94 = pkgs.haskell.packages.ghc94.callPackage ./default.nix {}; + richenv-ghc96 = pkgs.haskell.packages.ghc96.callPackage ./default.nix {}; + richenv-ghc98 = pkgs.haskell.packages.ghc98.callPackage ./default.nix {}; + # richenv-ghc910 = pkgs.haskell.packages.ghc910.callPackage ./default.nix {}; + }; }; }; } diff --git a/richenv.cabal b/richenv.cabal index 96ee430..cdeed71 100644 --- a/richenv.cabal +++ b/richenv.cabal @@ -21,7 +21,7 @@ name: richenv -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.1.0.1 +version: 0.1.0.2 -- A short (one-line) description of the package. synopsis: Rich environment variable setup for Haskell @@ -46,7 +46,7 @@ author: David Sánchez maintainer: davidslt+git@pm.me -- A copyright notice. -copyright: 2023 David Sánchez +copyright: 2024 David Sánchez category: Configuration build-type: Simple @@ -58,7 +58,7 @@ extra-doc-files: -- Extra source files to be distributed with the package, such as examples, or a tutorial module. -- extra-source-files: -tested-with: GHC ==9.2.8 || ==9.4.6 || ==9.6.2 +tested-with: GHC ==9.4.8 || ==9.6.5 || ==9.8.2 || ==9.10.1 source-repository head type: git @@ -66,10 +66,10 @@ source-repository head common common-options build-depends: - , aeson >=2.1.0 && <2.3 - , base >=4.14 && <5 - , text >=2.0 && <3 - , unordered-containers >=0.2.19 && <0.3 + , aeson >=2.1.2.1 && <2.3 + , base >=4.17 && <5 + , text >=2.0 && <3 + , unordered-containers >=0.2.20 && <0.3 ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns @@ -158,12 +158,13 @@ test-suite richenv-test -- Test dependencies. build-depends: , aeson - , bytestring >=0.11 && <0.13 - , hspec >=2.10 && <2.12 - , QuickCheck >=2.14 && <2.15 - , quickcheck-instances >=0.3.29 && <0.4 + , bytestring >=0.11 && <0.13 + , hspec >=2.10 && <2.12 + , HsYAML >=0.2.1.3 && <0.3 + , HsYAML-aeson >=0.2.0.1 && <0.3 + , QuickCheck >=2.14 && <2.16 + , quickcheck-instances >=0.3.29 && <0.4 , richenv - , yaml >=0.11 && <0.12 -- , process >=1.6 && <1.7 build-tool-depends: hspec-discover:hspec-discover diff --git a/test/RichEnvSpec.hs b/test/RichEnvSpec.hs index 724877e..f683433 100644 --- a/test/RichEnvSpec.hs +++ b/test/RichEnvSpec.hs @@ -1,14 +1,14 @@ module RichEnvSpec (spec) where import ArbitraryInstances () -import Control.Exception (displayException) import Data.Aeson qualified as JSON import Data.ByteString qualified as B import Data.ByteString.Char8 qualified as C8 import Data.HashMap.Strict qualified as HM import Data.List (sort) import Data.Text qualified as T -import Data.Yaml qualified as Yaml +import Data.YAML (Pos) +import Data.YAML.Aeson qualified as Yaml import GHC.Generics (Generic) import RichEnv (clearEnvironment, setRichEnvFromCurrent, toEnvListFromCurrent) import RichEnv.Types (Environment, Mappings (Mappings), Prefixes (Prefixes), RichEnv (..), Values (Values), defaultRichEnv, fromEnvironment, toEnvironment) @@ -75,9 +75,9 @@ spec = describe "RichEnv ops" $ do context "working with YAML" $ it "parses a YAML file into expected results" $ do clearEnv setTestEnv fileTestsBaseEnv - let res = Yaml.decodeEither' yamlTestCase :: Either Yaml.ParseException TestType + let res = Yaml.decode1Strict yamlTestCase case res of - Left err -> fail $ show err + Left err -> fail $ "Parse failed. State: " <> show err Right actual -> testEnvList fileTestsCaseExpected (environ actual) context "working with JSON" $ it "parses a JSON file into expected results" $ do @@ -90,10 +90,11 @@ spec = describe "RichEnv ops" $ do context "invariants" $ do prop "parsing YAML from and to a RichEnv should end in the original value" $ \re -> do - let yaml = Yaml.encode re - res = Yaml.decodeEither' yaml :: Either Yaml.ParseException RichEnv + let yaml = Yaml.encode1Strict re + C8.writeFile "./testcase.yaml" yaml + let res = Yaml.decode1Strict yaml :: Either (Pos, String) RichEnv in case res of - Left err -> fail $ displayException err + Left err -> fail $ "Parse failed. State: " <> show err Right actual -> do actual `shouldBe` re prop "parsing JSON from and to a RichEnv should end in the original value" $ \re -> do