diff --git a/.chglog/config.yml b/.chglog/config.yml index a5e40f7..31efa70 100755 --- a/.chglog/config.yml +++ b/.chglog/config.yml @@ -25,4 +25,4 @@ options: - Subject notes: keywords: - - BREAKING CHANGE \ No newline at end of file + - BREAKING CHANGE diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..7fab7bb --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,23 @@ +name: "Check, Test and Build Codebase" + +on: + pull_request: + workflow_dispatch: + +jobs: + check: + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout main" + uses: "actions/checkout@v4" + + - name: "Install Nix" + uses: "DeterminateSystems/nix-installer-action@v14" + + - name: "Use Nix Cache" + uses: "DeterminateSystems/magic-nix-cache-action@v8" + + - name: "Check, Test and Build" + run: | + nix-shell --pure --run "dev-test-build" diff --git a/.gitignore b/.gitignore index 7c95f5e..0a9d5a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,7 @@ -.cabal-sandbox/ -.ghc.environment.* -.hpc -.hsenv -.HTF/ -.stack-work/ -*.aux -*.chi -*.chs.h -*.dyn_hi -*.dyn_o -*.eventlog -*.hi -*.hie -*.hp -*.o -*.prof -cabal-dev -cabal.project.local -cabal.project.local~ -cabal.sandbox.config -dist -dist-* - -## Nix stuff: +*~ +/.direnv +/.envrc +/dist +/dist-newstyle /result +/tmp diff --git a/.hlint.yaml b/.hlint.yaml index b0ae94c..bb83dad 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -9,45 +9,45 @@ ####################### - modules: - - {name: Control.Monad.Error, within: []} - - {name: [Data.Aeson], as: Aeson} - - {name: Data.ByteString, as: B } - - {name: Data.ByteString.Char8, as: BC } - - {name: Data.ByteString.Lazy, as: BL } - - {name: Data.ByteString.Lazy.Char8, as: BLC } - - {name: Data.Text, as: T } - - {name: Data.Text.Lazy, as: TL } - - {name: Data.Text.Encoding, as: TE } + - { name: Control.Monad.Error, within: [] } + - { name: [Data.Aeson], as: Aeson } + - { name: Data.ByteString, as: B } + - { name: Data.ByteString.Char8, as: BC } + - { name: Data.ByteString.Lazy, as: BL } + - { name: Data.ByteString.Lazy.Char8, as: BLC } + - { name: Data.Text, as: T } + - { name: Data.Text.Lazy, as: TL } + - { name: Data.Text.Encoding, as: TE } ########################## # EXTENSION RESTRICTIONS # ########################## - extensions: - - default: false # All extension are banned by default - - name: - - DataKinds - - DeriveGeneric - - DeriveLift - - FlexibleContexts - - FlexibleInstances - - GADTs - - GeneralizedNewtypeDeriving - - KindSignatures - - OverloadedStrings - - RankNTypes - - RecordWildCards - - ScopedTypeVariables - - StandaloneDeriving - - TypeApplications - - TypeOperators + - default: false # All extension are banned by default + - name: + - DataKinds + - DeriveGeneric + - DeriveLift + - FlexibleContexts + - FlexibleInstances + - GADTs + - GeneralizedNewtypeDeriving + - KindSignatures + - OverloadedStrings + - RankNTypes + - RecordWildCards + - ScopedTypeVariables + - StandaloneDeriving + - TypeApplications + - TypeOperators ################ # CUSTOM RULES # ################ # Replace a $ b $ c with a . b $ c -- group: {name: dollar, enabled: true} +- group: { name: dollar, enabled: true } # Generalise map to fmap, ++ to <> -- group: {name: generalise, enabled: true} +- group: { name: generalise, enabled: true } diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a8a21b4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +dist-newstyle/ +dist/ +nix/ +*.md diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..e7ffdfe --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "tabWidth": 2, + "singleQuote": false, + "trailingComma": "es5", + "printWidth": 120 +} diff --git a/.stan.toml b/.stan.toml new file mode 100644 index 0000000..2b8e44b --- /dev/null +++ b/.stan.toml @@ -0,0 +1,8 @@ +# Big tuples +# Using tuples of big size (>= 4) can decrease code readability +# In serveral places Stack uses 4-tuples and in one place Stack uses a +# 5-tuple. +[[check]] + id = "STAN-0302" + scope = "all" + type = "Exclude" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 86d41b3..0000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2021-2022 Telostat Pte Ltd - -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/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2dfeb9a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2024 Telostat Pte Ltd + +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 index 0388770..9f2e4b2 100644 --- a/README.md +++ b/README.md @@ -13,123 +13,31 @@ *haspara* is a Haskell library that provides monetary definitions and a rudimentary (and experimental) accounting functionality. -## Supported GHC Versions - -1. `ghc90` -1. `ghc92` - -At the moment, there is no particular reason for not supporting -`ghc94` except that required settings are not done yet in Nix support -files, in particular for Cabal dependency. - -## Testing Against GHC Versions - -You can use Nix support to test against different GHC versions: - -```sh -nix-build --arg compiler "\"ghc92\"" -nix-build --arg compiler "\"ghc90\"" -``` - -`nix-build` command will default to `ghc90`. Check `./default.nix` -file for the default `ghc` version in case that this documentation is -out of date. - ## Development -Before committing code to repository, reformat the code: +Big, long build command for the impatient: ```sh -fourmolu -i src/ test/ -``` - -Compile the codebase, check warnings and errors: - -```sh -cabal build -O0 -cabal test -O0 -cabal haddock -O0 -``` - -Run [hlint](https://github.com/ndmitchell/hlint): - -```sh -hlint src/ +hpack && + direnv reload && + fourmolu -i src/ test/ && + prettier --write . && + find . -iname "*.nix" -not -path "*/nix/sources.nix" -print0 | xargs --null nixpkgs-fmt && + hlint src/ test/ && + cabal build -O0 && + cabal run -O0 haspara -- --version && + cabal v1-test && + cabal haddock -O0 ``` -Run [weeder](https://hackage.haskell.org/package/weeder): +To test and build codebase in development environment, run: ```sh -weeder --require-hs-files +dev-test-build ``` -## Making Releases - -1. Checkout `main` branch: - - ```sh - git checkout main - ``` - -2. Ensure that your branch is up to date: - - ```sh - git pull - ``` - -3. Update the `version` information in [package.yaml](./package.yaml) if - required, run `hpack` to reflect the change on the `.cabal` file, and - recompile the project, run tests and generate Haddock documentation: - - ```sh - hpack - cabal clean - cabal build -O0 - cabal test -O0 - cabal haddock -O0 - nix-build --arg compiler "\"ghc92\"" - nix-build --arg compiler "\"ghc90\"" - ``` - -4. Update [CHANGELOG.md](./CHANGELOG.md) file: - - ```sh - git-chglog --next-tag -o CHANGELOG.md - ``` - -5. Commit, tag and push: - - ```sh - git commit -am "chore(release): " - git tag -a -m "Release " - git push --follow-tags origin main - ``` - -6. Publish GitHub release: - - ```sh - gh release create "" --title "v" --generate-notes - ``` - -7. Create the package, upload to Hackage as a candidate first and check the result: - - ```sh - cabal clean - cabal build -O0 - cabal test -O0 - cabal haddock -O0 - cabal sdist - cabal upload - ``` - -8. If the candidate package release works fine, release to Hackage: - - ```sh - cabal upload --publish - ``` - ## License -Copyright Telostat Pte Ltd (c) 2021-2022. +Copyright Telostat Pte Ltd (c) 2021-2024. This work is licensed under MIT license. See [LICENSE](./LICENSE). diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..fdadb42 --- /dev/null +++ b/cabal.project @@ -0,0 +1,5 @@ +packages: + *.cabal + +package * + ghc-options: -fwrite-ide-info diff --git a/default.nix b/default.nix index 842a4b7..722a3ff 100644 --- a/default.nix +++ b/default.nix @@ -1,9 +1,103 @@ -{ compiler ? "ghc90" +{ sources ? import ./nix/sources.nix +, compiler ? "default" +, system ? builtins.currentSystem , ... }: - let - nix = import ./nix { compiler = compiler; }; + ################## + ## LOAD NIXPKGS ## + ################## + + ## Import nixpkgs pinned by niv: + pkgs = import sources.nixpkgs { inherit system; }; + + ################## + ## LOAD HELPERS ## + ################## + + ## Load the YAML reader: + readYAML = pkgs.callPackage ./nix/lib/read-yaml.nix { }; + + ## Load Haskell package factory: + mkHaskell = pkgs.callPackage ./nix/lib/mk-haskell.nix { }; + + ########################### + ## ESSENTIAL INFORMATION ## + ########################### + + ## Get the main Haskell package specification: + packageSpec = readYAML (thisHaskellPackages.main.path + "/package.yaml"); + + ############# + ## HASKELL ## + ############# + + ## Get Haskell packages in the project: + thisHaskellPackages = { + main = { + name = packageSpec.name; + path = ./.; + }; + subs = [ ]; + }; + + ## Get Haskell packages in the project as a list: + thisHaskellPackagesAll = [ thisHaskellPackages.main ] ++ thisHaskellPackages.subs; + + ## Get base Haskell package set: + baseHaskell = if compiler == "default" then pkgs.haskellPackages else pkgs.haskell.packages.${compiler}; + + ## Get this Haskell package set: + thisHaskell = mkHaskell { + haskell = baseHaskell; + packages = thisHaskellPackagesAll; + overrides = self: super: { }; + }; + + ########### + ## SHELL ## + ########### + + ## Prepare dev-test-build script: + dev-test-build = pkgs.writeShellApplication { + name = "dev-test-build"; + text = builtins.readFile ./nix/dev-test-build.sh; + runtimeInputs = [ pkgs.bash pkgs.bc pkgs.moreutils ]; + }; + + ## Prepare Nix shell: + thisShell = thisHaskell.shellFor { + ## Define packages for the shell: + packages = p: builtins.map (x: p.${x.name}) thisHaskellPackagesAll; + + ## Enable Hoogle: + withHoogle = false; + + ## Build inputs for development shell: + buildInputs = [ + ## Haskell related build inputs: + thisHaskell.apply-refact + thisHaskell.cabal-fmt + thisHaskell.cabal-install + thisHaskell.cabal2nix + thisHaskell.fourmolu + thisHaskell.haskell-language-server + thisHaskell.hlint + thisHaskell.hpack + thisHaskell.weeder + + ## Other build inputs for various development requirements: + pkgs.docker-client + pkgs.git + pkgs.nil + pkgs.nixpkgs-fmt + pkgs.nodePackages.prettier + pkgs.upx + dev-test-build + ]; + }; in -nix.thisPackage +{ + shell = thisShell; +} diff --git a/fourmolu.yaml b/fourmolu.yaml index a319859..892756c 100644 --- a/fourmolu.yaml +++ b/fourmolu.yaml @@ -1,10 +1,18 @@ indentation: 2 +column-limit: none function-arrows: leading comma-style: leading -import-export-comma-style: leading +import-export-style: diff-friendly indent-wheres: true record-brace-space: true newlines-between-decls: 2 haddock-style: single-line +haddock-style-module: single-line +let-style: inline +in-style: right-align +single-constraint-parens: never +unicode: never respectful: false fixities: [] +reexports: [] +single-deriving-parens: always diff --git a/haspara.cabal b/haspara.cabal index 11e4c19..45d2d6e 100644 --- a/haspara.cabal +++ b/haspara.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.7. +-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack @@ -13,9 +13,8 @@ homepage: https://github.com/telostat/haspara#readme bug-reports: https://github.com/telostat/haspara/issues author: Vehbi Sinan Tunalioglu maintainer: vst@vsthost.com -copyright: Copyright (c) 2021-2022 Telostat Pte Ltd +copyright: Copyright (c) 2021-2024 Telostat Pte Ltd license: MIT -license-file: LICENSE build-type: Simple extra-source-files: README.md @@ -49,20 +48,20 @@ library src ghc-options: -Wall -Wunused-packages build-depends: - aeson >=1.5.6.0 && <2.1 + aeson >=1.5.6.0 && <2.3 , base >=4.11 && <5 , containers >=0.6.4.1 && <0.7 , data-default ==0.7.* , exceptions >=0.10.4 && <0.11 , hashable >=1.3.0.0 && <1.5 - , megaparsec >=9.0.1 && <9.3 - , mtl >=2.2.2 && <2.3 - , refined >=0.6.3 && <0.7 + , megaparsec >=9.0.1 && <9.6 + , mtl >=2.2.2 && <2.4 + , refined >=0.6.3 && <0.9 , safe-decimal >=0.2.1.0 && <0.3 , scientific >=0.3.7.0 && <0.4 - , template-haskell >=2.16.0.0 && <2.19 - , text >=1.2.4.1 && <1.3 - , time >=1.9.3 && <1.12 + , template-haskell >=2.16.0.0 && <2.21 + , text >=1.2.4.1 && <2.2 + , time >=1.9.3 && <1.14 default-language: Haskell2010 test-suite haspara-doctest @@ -71,22 +70,23 @@ test-suite haspara-doctest other-modules: Paths_haspara hs-source-dirs: - ./ - ghc-options: -threaded + test/doctest + ghc-options: -Wall -threaded build-depends: base >=4.11 && <5 , doctest + , haspara default-language: Haskell2010 test-suite haspara-test type: exitcode-stdio-1.0 - main-is: test.hs + main-is: Spec.hs other-modules: Tests.Haspara.Accounting.Inventory Paths_haspara hs-source-dirs: - test - ghc-options: -Wall -Wunused-packages -threaded + test/spec + ghc-options: -Wall -Wunused-packages -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.11 && <5 , containers diff --git a/nix/default.nix b/nix/default.nix deleted file mode 100644 index f34213e..0000000 --- a/nix/default.nix +++ /dev/null @@ -1,57 +0,0 @@ -{ compiler ? "ghc90" -, ... -}: - -let - ## Import sources: - sources = import ./sources.nix; - - ## Import telosnix: - telosnix = import sources.telosnix { }; - - ## Import nixpkgs: - pkgs = import telosnix.pkgs-sources.unstable { }; - - ## Get Haskell for package development purposes: - haskell = telosnix.tools.haskell.getHaskell - { - pkgs = pkgs; - compiler = compiler; - }; - - ## Get this package: - thisPackage = haskell.callCabal2nix "haspara" ../. { }; - - ## Get this package's Haskell dependencies: - thisPackageDeps = pkgs.haskell.lib.compose.getHaskellBuildInputs thisPackage; - - ## Get our GHC for development: - thisGhc = haskell.ghcWithPackages (_: thisPackageDeps); - - ## Get Haskell development tools: - haskell-dev-tools = with haskell; - [ - ## Our GHC with all packages required to build and test our package: - thisGhc - - ## Various haskell tools: - apply-refact - cabal-install - cabal2nix - fourmolu - haskell-language-server - hlint - hpack - weeder - ]; -in -{ - sources = sources; - telosnix = telosnix; - pkgs = pkgs; - haskell = haskell; - thisPackage = thisPackage; - thisPackageDeps = thisPackageDeps; - ghc = thisGhc; - haskell-dev-tools = haskell-dev-tools; -} diff --git a/nix/dev-test-build.sh b/nix/dev-test-build.sh new file mode 100644 index 0000000..6b58e14 --- /dev/null +++ b/nix/dev-test-build.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +## Purpose: This script is used to run all the necessary checks and +## tests for the project. + +## Fail on any error: +set -e + +## Declare default styles: +_sty_bold="" +_sty_underline="" +_sty_standout="" +_sty_normal="" +_sty_black="" +_sty_red="" +_sty_green="" +_sty_yellow="" +_sty_blue="" +_sty_magenta="" +_sty_cyan="" +_sty_white="" + +## Set styles if we are on terminal: +if test -t 1; then + ## Check if the terminal supports colors: + ncolors=$(tput colors) + + ## Defines styles: + if test -n "$ncolors" && test "${ncolors}" -ge 8; then + _sty_bold="$(tput bold)" + _sty_underline="$(tput smul)" + _sty_standout="$(tput smso)" + _sty_normal="$(tput sgr0)" + _sty_black="$(tput setaf 0)" + _sty_red="$(tput setaf 1)" + _sty_green="$(tput setaf 2)" + _sty_yellow="$(tput setaf 3)" + _sty_blue="$(tput setaf 4)" + _sty_magenta="$(tput setaf 5)" + _sty_cyan="$(tput setaf 6)" + _sty_white="$(tput setaf 7)" + fi +fi + +_clean="" + +while getopts ":c" opt; do + case ${opt} in + c) + _clean="true" + ;; + ?) + echo "Invalid option: -${OPTARG}." + exit 1 + ;; + esac +done + +_get_now() { + t=${EPOCHREALTIME} # remove the decimal separator (s → µs) + t=${t%???} # remove the last three digits (µs → ms) + echo "${t}" +} + +_get_diff() { + printf "scale=3; %s - %s\n" "${2}" "${1}" | bc +} + +_print_header() { + printf "${_sty_bold}${_sty_blue}🔵 Running %s${_sty_normal}" "${1}" +} + +_print_success() { + _start="${1}" + _until="${2}" + _elapsed=$(_get_diff "${_start}" "${_until}") + printf "${_sty_bold}${_sty_green} ✅ %ss${_sty_normal}\n" "${_elapsed}" +} + +_clean() { + _print_header "clean" + _start=$(_get_now) + chronic -- cabal clean && chronic -- cabal v1-clean + _print_success "${_start}" "$(_get_now)" +} + +_hpack() { + _print_header "hpack (v$(hpack --numeric-version))" + _start=$(_get_now) + chronic -- hpack + _print_success "${_start}" "$(_get_now)" +} + +_fourmolu() { + _print_header "fourmolu (v$(fourmolu --version | head -n1 | cut -d' ' -f2))" + _start=$(_get_now) + chronic -- fourmolu --quiet --mode check src/ test/ + _print_success "${_start}" "$(_get_now)" +} + +_prettier() { + _print_header "prettier (v$(prettier --version))" + _start=$(_get_now) + chronic -- prettier --check . + _print_success "${_start}" "$(_get_now)" +} + +_nixpkgs_fmt() { + _print_header "nixpkgs-fmt (v$(nixpkgs-fmt --version 2>&1 | cut -d' ' -f2))" + _start=$(_get_now) + chronic -- find . -iname "*.nix" -not -path "*/nix/sources.nix" -exec nixpkgs-fmt --check {} \; + _print_success "${_start}" "$(_get_now)" +} + +_hlint() { + _print_header "hlint (v$(hlint --numeric-version))" + _start=$(_get_now) + chronic -- hlint src/ test/ + _print_success "${_start}" "$(_get_now)" +} + +_cabal_build() { + _print_header "cabal build (v$(cabal --numeric-version))" + _start=$(_get_now) + chronic -- cabal build -O0 + _print_success "${_start}" "$(_get_now)" +} + +_cabal_test() { + _print_header "cabal test (v$(cabal --numeric-version))" + _start=$(_get_now) + chronic -- cabal v1-test + _print_success "${_start}" "$(_get_now)" +} + +_weeder() { + _print_header "weeder (v$(weeder --version | head -n1 | cut -d' ' -f3))" + _start=$(_get_now) + chronic -- weeder + _print_success "${_start}" "$(_get_now)" +} + +_cabal_haddock() { + _print_header "cabal haddock (v$(cabal --numeric-version))" + _start=$(_get_now) + chronic -- cabal haddock -O0 \ + --haddock-quickjump \ + --haddock-hyperlink-source \ + --haddock-html-location="https://hackage.haskell.org/package/\$pkg-\$version/docs" + _print_success "${_start}" "$(_get_now)" +} + +_scr_start=$(_get_now) +if [ -n "${_clean}" ]; then + _clean +fi +_hpack +_fourmolu +_prettier +_nixpkgs_fmt +_hlint +_cabal_build +_cabal_test +_weeder +_cabal_haddock +printf "Finished all in %ss\n" "$(_get_diff "${_scr_start}" "$(_get_now)")" diff --git a/nix/lib/mk-haskell.nix b/nix/lib/mk-haskell.nix new file mode 100644 index 0000000..64514be --- /dev/null +++ b/nix/lib/mk-haskell.nix @@ -0,0 +1,15 @@ +{ pkgs, ... }: + +## Function that makes a Haskell. +{ haskell +, packages +, overrides +}: +let + packageFromSpec = self: { name, path }: { + ${name} = self.callCabal2nix name path { }; + }; +in +haskell.override { + overrides = self: super: builtins.foldl' (a: c: a // packageFromSpec self c) (overrides self super) packages; +} diff --git a/nix/lib/read-yaml.nix b/nix/lib/read-yaml.nix new file mode 100644 index 0000000..d89721a --- /dev/null +++ b/nix/lib/read-yaml.nix @@ -0,0 +1,27 @@ +## This file is a verbatim copy of: +## +## https://github.com/cdepillabout/stacklock2nix/blob/8408f57e929ca713e508f45dc3d846eca20c3379/nix/build-support/stacklock2nix/read-yaml.nix + +{ runCommand, remarshal }: + +# Read a YAML file into a Nix datatype using IFD. +# +# Similar to: +# +# > builtins.fromJSON (builtins.readFile ./somefile) +# +# but takes an input file in YAML instead of JSON. +# +# readYAML :: Path -> a +# +# where `a` is the Nixified version of the input file. +path: + +let + jsonOutputDrv = + runCommand + "from-yaml" + { nativeBuildInputs = [ remarshal ]; } + "remarshal -if yaml -i \"${path}\" -of json -o \"$out\""; +in +builtins.fromJSON (builtins.readFile jsonOutputDrv) diff --git a/nix/sources.json b/nix/sources.json index d842c0e..b90c0b7 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -1,14 +1,14 @@ { - "telosnix": { - "branch": "v0.0.4", - "description": null, + "nixpkgs": { + "branch": "release-24.05", + "description": "Nix Packages collection", "homepage": null, - "owner": "telostat", - "repo": "telos.nix", - "rev": "4d45ee0f57fb6979781b726cdf87eb68f4ef672d", - "sha256": "0my9pmbvyfpj3n747zmwhl6fk8mhnmirl18zmlpy29g1pcd891k2", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "24f1a65397b6c1c7c71733f2e68f3ad7b8e6c139", + "sha256": "164fid4jf62rdh6dg8b3m8nb5j5xv03psakzfclkfnmnp3ig9mvi", "type": "tarball", - "url": "https://github.com/telostat/telos.nix/archive/4d45ee0f57fb6979781b726cdf87eb68f4ef672d.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/24f1a65397b6c1c7c71733f2e68f3ad7b8e6c139.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/sources.nix b/nix/sources.nix index 9a01c8a..fe3dadf 100644 --- a/nix/sources.nix +++ b/nix/sources.nix @@ -10,33 +10,34 @@ let let name' = sanitizeName name + "-src"; in - if spec.builtin or true then - builtins_fetchurl { inherit (spec) url sha256; name = name'; } - else - pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; fetch_tarball = pkgs: name: spec: let name' = sanitizeName name + "-src"; in - if spec.builtin or true then - builtins_fetchTarball { name = name'; inherit (spec) url sha256; } - else - pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; fetch_git = name: spec: let ref = - if spec ? ref then spec.ref else + spec.ref or ( if spec ? branch then "refs/heads/${spec.branch}" else - if spec ? tag then "refs/tags/${spec.tag}" else - abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; - submodules = if spec ? submodules then spec.submodules else false; + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" + ); + submodules = spec.submodules or false; submoduleArg = let nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; emptyArgWithWarning = - if submodules == true + if submodules then builtins.trace ( @@ -44,15 +45,15 @@ let + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + "does not support them" ) - {} - else {}; + { } + else { }; in - if nixSupportsSubmodules - then { inherit submodules; } - else emptyArgWithWarning; + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; in - builtins.fetchGit - ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); fetch_local = spec: spec.path; @@ -86,16 +87,16 @@ let hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; hasThisAsNixpkgsPath = == ./.; in - if builtins.hasAttr "nixpkgs" sources - then sourcesNixpkgs - else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then - import {} - else - abort - '' - Please specify either (through -I or NIX_PATH=nixpkgs=...) or - add a package called "nixpkgs" to your sources.json. - ''; + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import { } + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; # The actual fetching function. fetch = pkgs: name: spec: @@ -115,13 +116,13 @@ let # the path directly as opposed to the fetched source. replace = name: drv: let - saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; in - if ersatz == "" then drv else - # this turns the string into an actual Nix path (for both absolute and - # relative paths) - if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; # Ports of functions for older nix versions @@ -132,7 +133,7 @@ let ); # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 - range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); @@ -143,43 +144,46 @@ let concatStrings = builtins.concatStringsSep ""; # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 - optionalAttrs = cond: as: if cond then as else {}; + optionalAttrs = cond: as: if cond then as else { }; # fetchTarball version that is compatible between all the versions of Nix builtins_fetchTarball = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchTarball; in - if lessThan nixVersion "1.12" then - fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchTarball attrs; + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchTarball attrs; # fetchurl version that is compatible between all the versions of Nix builtins_fetchurl = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchurl; in - if lessThan nixVersion "1.12" then - fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchurl attrs; + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchurl attrs; # Create the final "sources" from the config mkSources = config: - mapAttrs ( - name: spec: - if builtins.hasAttr "outPath" spec - then abort - "The values in sources.json should not have an 'outPath' attribute" - else - spec // { outPath = replace name (fetch config.pkgs name spec); } - ) config.sources; + mapAttrs + ( + name: spec: + if builtins.hasAttr "outPath" spec + then + abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) + config.sources; # The "config" used by the fetchers mkConfig = { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null - , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) , system ? builtins.currentSystem , pkgs ? mkPkgs sources system }: rec { @@ -191,4 +195,4 @@ let }; in -mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } +mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/package.yaml b/package.yaml index 8bf356c..ec3006d 100644 --- a/package.yaml +++ b/package.yaml @@ -1,61 +1,64 @@ -name: haspara -version: 0.0.0.8 -github: "telostat/haspara" -license: MIT -author: "Vehbi Sinan Tunalioglu" -maintainer: "vst@vsthost.com" -copyright: "Copyright (c) 2021-2022 Telostat Pte Ltd" - -category: Finance -synopsis: A library providing definitions to work with monetary values. -description: Please see the README on GitHub at +name: haspara +version: 0.0.0.8 +github: "telostat/haspara" +license: MIT +author: "Vehbi Sinan Tunalioglu" +maintainer: "vst@vsthost.com" +copyright: "Copyright (c) 2021-2024 Telostat Pte Ltd" +category: Finance +synopsis: A library providing definitions to work with monetary values. +description: Please see the README on GitHub at extra-source-files: -- README.md -- CHANGELOG.md + - README.md + - CHANGELOG.md dependencies: -- base >= 4.11 && < 5 + - base >= 4.11 && < 5 library: - source-dirs: src + source-dirs: src ghc-options: - - -Wall - - -Wunused-packages + - -Wall + - -Wunused-packages dependencies: - - aeson >=1.5.6.0 && <2.1 - - containers >=0.6.4.1 && <0.7 - - data-default >= 0.7 && <0.8 - - exceptions >=0.10.4 && <0.11 - - hashable >=1.3.0.0 && <1.5 - - megaparsec >=9.0.1 && <9.3 - - mtl >=2.2.2 && <2.3 - - refined >=0.6.3 && <0.7 - - safe-decimal >=0.2.1.0 && <0.3 - - scientific >=0.3.7.0 && <0.4 - - template-haskell >=2.16.0.0 && <2.19 - - text >=1.2.4.1 && <1.3 - - time >=1.9.3 && <1.12 + - aeson >=1.5.6.0 && <2.3 + - containers >=0.6.4.1 && <0.7 + - data-default >= 0.7 && <0.8 + - exceptions >=0.10.4 && <0.11 + - hashable >=1.3.0.0 && <1.5 + - megaparsec >=9.0.1 && <9.6 + - mtl >=2.2.2 && <2.4 + - refined >=0.6.3 && <0.9 + - safe-decimal >=0.2.1.0 && <0.3 + - scientific >=0.3.7.0 && <0.4 + - template-haskell >=2.16.0.0 && <2.21 + - text >=1.2.4.1 && <2.2 + - time >=1.9.3 && <1.14 tests: haspara-test: - main: test.hs - source-dirs: test + main: Spec.hs + source-dirs: test/spec ghc-options: - - -Wall - - -Wunused-packages - - -threaded + - -Wall + - -Wunused-packages + - -threaded + - -rtsopts + - -with-rtsopts=-N dependencies: - - containers - - data-default - - haspara - - hspec - - time + - containers + - data-default + - haspara + - hspec + - time haspara-doctest: - main: doctest.hs - source-dirs: . + main: doctest.hs + source-dirs: test/doctest ghc-options: - - -threaded + - -Wall + - -threaded dependencies: - - doctest + - haspara + - doctest diff --git a/shell.nix b/shell.nix index b5c8b65..fbd4670 100644 --- a/shell.nix +++ b/shell.nix @@ -1,30 +1 @@ -{ ... }: - -let - ## Import this codebase's Nix helper set: - nix = import ./nix { }; - - ## Get packages: - pkgs = nix.pkgs; -in -pkgs.mkShell { - buildInputs = [ - ## Fancy stuff: - pkgs.figlet - pkgs.lolcat - - ## Release stuff: - pkgs.busybox - pkgs.gh - pkgs.git - pkgs.git-chglog - ] ++ nix.haskell-dev-tools; - - shellHook = '' - figlet -w 999 "HASPARA DEV SHELL" | lolcat -S 42 - - ## Make sure that doctest finds correct GHC executable and libraries: - export NIX_GHC=${nix.ghc}/bin/ghc - export NIX_GHC_LIBDIR=${nix.ghc}/lib/${nix.ghc.meta.name} - ''; -} +(import ./default.nix { }).shell diff --git a/src/Haspara/Quantity.hs b/src/Haspara/Quantity.hs index 3c7ff6a..ba2dfdc 100644 --- a/src/Haspara/Quantity.hs +++ b/src/Haspara/Quantity.hs @@ -15,7 +15,6 @@ -- with fixed decimal points. module Haspara.Quantity where -import Control.Applicative (liftA2) import Control.Monad.Except (MonadError (throwError)) import qualified Data.Aeson as Aeson import qualified Data.Aeson.Encoding as Aeson.Encoding @@ -91,7 +90,7 @@ deriving instance TH.Lift (Quantity s) -- Just 0.42 -- >>> Aeson.decode "0.425" :: Maybe (Quantity 2) -- Just 0.42 -instance (KnownNat s) => Aeson.FromJSON (Quantity s) where +instance KnownNat s => Aeson.FromJSON (Quantity s) where parseJSON = Aeson.withScientific "Quantity" (pure . mkQuantity) @@ -99,7 +98,7 @@ instance (KnownNat s) => Aeson.FromJSON (Quantity s) where -- -- >>> Aeson.encode (mkQuantity 0.42 :: Quantity 2) -- "0.42" -instance (KnownNat s) => Aeson.ToJSON (Quantity s) where +instance KnownNat s => Aeson.ToJSON (Quantity s) where toJSON = Aeson.Number . D.toScientificDecimal . unQuantity toEncoding = Aeson.Encoding.scientific . D.toScientificDecimal . unQuantity @@ -126,7 +125,7 @@ instance (KnownNat s) => Aeson.ToJSON (Quantity s) where -- Right 42.00 -- >>> arithM (fromInteger 42) :: Either SomeException (Quantity 2) -- Right 42.00 -instance (KnownNat s) => Num (D.Arith (Quantity s)) where +instance KnownNat s => Num (D.Arith (Quantity s)) where (+) = liftA2 (+) (-) = liftA2 (-) (*) = liftA2 (*) @@ -153,7 +152,7 @@ instance (KnownNat s) => Num (D.Arith (Quantity s)) where -- Right 42.00 -- >>> arithM (Arith a / Arith b / Arith c) :: Either SomeException (Quantity 2) -- Left divide by zero -instance (KnownNat s) => Fractional (D.Arith (Quantity s)) where +instance KnownNat s => Fractional (D.Arith (Quantity s)) where a / b = fmap MkQuantity $ fmap unQuantity a / fmap unQuantity b fromRational = fmap MkQuantity . D.fromRationalDecimalWithoutLoss @@ -318,7 +317,7 @@ timesLossless (MkQuantity d1) (MkQuantity d2) = MkQuantity (D.timesDecimal d1 d2 -- Just 2.00 -- >>> divide (mkQuantity 0.42 :: Quantity 2) (mkQuantity (-0.21) :: Quantity 2) -- Just -2.00 -divide :: (KnownNat s) => Quantity s -> Quantity s -> Maybe (Quantity s) +divide :: KnownNat s => Quantity s -> Quantity s -> Maybe (Quantity s) divide (MkQuantity d1) (MkQuantity d2) = MkQuantity <$> D.divideDecimalWithRounding d1 d2 diff --git a/doctest.hs b/test/doctest/doctest.hs similarity index 54% rename from doctest.hs rename to test/doctest/doctest.hs index 4b79db8..7a40c54 100644 --- a/doctest.hs +++ b/test/doctest/doctest.hs @@ -2,6 +2,4 @@ import Test.DocTest (doctest) main :: IO () -main = - doctest - ["-isrc" , "src"] +main = doctest ["-isrc", "src"] diff --git a/test/test.hs b/test/spec/Spec.hs similarity index 100% rename from test/test.hs rename to test/spec/Spec.hs diff --git a/test/Tests/Haspara/Accounting/Inventory.hs b/test/spec/Tests/Haspara/Accounting/Inventory.hs similarity index 100% rename from test/Tests/Haspara/Accounting/Inventory.hs rename to test/spec/Tests/Haspara/Accounting/Inventory.hs diff --git a/weeder.dhall b/weeder.dhall deleted file mode 100644 index 5c0f38f..0000000 --- a/weeder.dhall +++ /dev/null @@ -1,4 +0,0 @@ -{ roots = - [ "^Haspara.Accounting\$", "^Haspara.TH\$", "^Main.main\$", "^Paths_.*" ] -, type-class-roots = True -} diff --git a/weeder.toml b/weeder.toml new file mode 100644 index 0000000..c4d2c6a --- /dev/null +++ b/weeder.toml @@ -0,0 +1,12 @@ +roots = [ + ## Definitions we always need: + "^Main.main$", + "^Haspara.*", + "^Haspara.Accounting.*", + "^Haspara.TH.*", + "^Paths_haspara.*", + + ## Temporary suspensions: + "^Zamazingo.*", +] +type-class-roots = true