From 9dae30ae709ac9b9b14914409b8c3bc1f5f859fe Mon Sep 17 00:00:00 2001 From: Nick Seagull Date: Tue, 30 Jul 2024 12:12:25 +0100 Subject: [PATCH] chore: CI/CD (#98) * Add coderabbit config * Add basic CICD * Add cabal update step * Try using devenv script * Typo * Update * Didnt save this one... * Cache? * Fix test * Update * Remove tests * Update * Attempt to fix install * Trigger CI * Update * Retrigger * Update * Retrigger * Always run cabal update --------- Co-authored-by: NickSeagull --- .coderabbit.yaml | 95 ++++++++++++++++++++++ .github/workflows/test.yml | 57 ++++++++++++++ cli/src/Neo.hs | 4 +- cli/test/Main.hs | 4 +- core/core/Array.hs | 157 +++++++++++++------------------------ core/core/Maybe.hs | 45 +++-------- core/core/Unknown.hs | 14 ---- core/nhcore.cabal | 2 + core/test/Main.hs | 4 +- devenv.lock | 12 +-- devenv.nix | 15 +++- 11 files changed, 249 insertions(+), 160 deletions(-) create mode 100644 .coderabbit.yaml create mode 100644 .github/workflows/test.yml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..d14b715 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,95 @@ +language: en-US +tone_instructions: >- + You must use a respectful, but commanding tone, as if you were impersonating an AI deity. +early_access: true +enable_free_tier: true +reviews: + profile: assertive + request_changes_workflow: true + high_level_summary: true + high_level_summary_placeholder: '@coderabbitai summary' + auto_title_placeholder: '@coderabbitai' + review_status: true + poem: true + collapse_walkthrough: false + sequence_diagrams: true + path_filters: [] + path_instructions: + - path: ".hlint.yaml" + instructions: | + Ignore this file + - path: "*.hs" + instructions: | + Remember that this is a NeoHaskell file. NeoHaskell is a + Haskell dialect that is inspired by Elm, therefore the + Elm style and conventions should be followed. Also, + Elm core libs are available, and the Haskell Prelude is + ignored, as the NoImplicitPrelude extension is enabled. + abort_on_close: true + auto_review: + enabled: true + auto_incremental_review: true + ignore_title_keywords: [] + labels: [] + drafts: false + base_branches: [] + tools: + shellcheck: + enabled: true + ruff: + enabled: false + markdownlint: + enabled: true + github-checks: + enabled: true + timeout_ms: 90000 + languagetool: + enabled: true + enabled_only: false + level: default + enabled_rules: [] + disabled_rules: + - EN_UNPAIRED_BRACKETS + enabled_categories: [] + disabled_categories: + - TYPOS + - TYPOGRAPHY + - CASING + biome: + enabled: true + hadolint: + enabled: true + swiftlint: + enabled: true + phpstan: + enabled: true + level: default + golangci-lint: + enabled: true + yamllint: + enabled: true + gitleaks: + enabled: true + checkov: + enabled: true + detekt: + enabled: true + eslint: + enabled: true + ast-grep: + packages: [] + rule_dirs: [] + util_dirs: [] + essential_rules: true +chat: + auto_reply: true +knowledge_base: + opt_out: false + learnings: + scope: global + issues: + scope: global + jira: + project_keys: [] + linear: + team_keys: [] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7d3087f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,57 @@ +name: "Test" + +on: + pull_request: + branches: + - main + push: + branches: + - main + +defaults: + run: + shell: devenv shell bash -- -e {0} + +jobs: + tests: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v26 + - uses: cachix/cachix-action@v14 + with: + name: devenv + + - name: Install devenv.sh + shell: sh + run: nix profile install nixpkgs#devenv + + - name: Cache Cabal + id: cache + uses: actions/cache@v4 + with: + path: | + ~/.cabal + dist-newstyle + .devenv/profile + key: ${{ runner.os }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/*.lock') }}-1 # modify the key if the cache is not working as expected + + - name: Update Cabal Hackage list + # if: steps.cache.outputs.cache-hit != 'true' + run: run-update + + # - name: Build the devenv shell and run any pre-commit hooks + # run: devenv test + + - name: Build the project + run: run-build + + - name: Run core tests + run: run-core-tests + + - name: Run CLI tests + run: run-cli-tests diff --git a/cli/src/Neo.hs b/cli/src/Neo.hs index 45bbb76..008ee4e 100644 --- a/cli/src/Neo.hs +++ b/cli/src/Neo.hs @@ -69,9 +69,9 @@ update message model = ProjectFileParsed projectDefinition -> (model {project = Just projectDefinition}, Command.none) BuildStarted -> - (model {status = "Build Started"}, Command.none) + (model {status = "Build Started!"}, Command.none) BuildFailed _ -> - (model {status = "Build Failed"}, Command.none) + (model {status = "Build Failed!"}, Command.none) view :: Model -> Text view m = diff --git a/cli/test/Main.hs b/cli/test/Main.hs index 3e2059e..dadc637 100644 --- a/cli/test/Main.hs +++ b/cli/test/Main.hs @@ -1,4 +1,6 @@ module Main (main) where +import Core + main :: IO () -main = putStrLn "Test suite not yet implemented." +main = print "Test suite not yet implemented." diff --git a/core/core/Array.hs b/core/core/Array.hs index 098fde4..0bd2eb4 100644 --- a/core/core/Array.hs +++ b/core/core/Array.hs @@ -1,38 +1,39 @@ -- | Fast immutable arrays. The elements in an array must have the same type. -module Array ( - -- * Arrays - Array, +module Array + ( -- * Arrays + Array, - -- * Creation - empty, - initialize, - repeat, - fromLinkedList, + -- * Creation + empty, + initialize, + repeat, + fromLinkedList, - -- * Query - isEmpty, - length, - get, + -- * Query + isEmpty, + length, + get, - -- * Manipulate - set, - push, - append, - slice, + -- * Manipulate + set, + push, + append, + slice, - -- * LinkedLists - toLinkedList, - toIndexedLinkedList, + -- * LinkedLists + toLinkedList, + toIndexedLinkedList, - -- * Transform - map, - indexedMap, - foldr, - foldl, - takeIf, - flatMap, - forEach, -) where + -- * Transform + map, + indexedMap, + foldr, + foldl, + takeIf, + flatMap, + forEach, + ) +where import Basics import Data.Foldable qualified @@ -42,28 +43,30 @@ import GHC.IsList qualified as GHC import LinkedList (LinkedList) import LinkedList qualified import Maybe (Maybe (..)) +import Test.QuickCheck qualified as QuickCheck import Tuple qualified import Prelude qualified - -- | Representation of fast immutable arrays. You can create arrays of integers -- (@Array Int@) or strings (@Array String@) or any other type of value you can -- dream up. newtype Array a = Array (Data.Vector.Vector a) deriving (Prelude.Eq, Prelude.Show) +instance (QuickCheck.Arbitrary a) => QuickCheck.Arbitrary (Array a) where + arbitrary = do + list <- QuickCheck.arbitrary + pure (fromLinkedList list) instance GHC.IsList (Array a) where type Item (Array a) = a fromList = Basics.fromList toList = toLinkedList - -- | Helper function to unwrap an array unwrap :: Array a -> Data.Vector.Vector a unwrap (Array v) = v - -- | Return an empty array. -- -- > length empty == 0 @@ -71,14 +74,12 @@ empty :: Array a empty = Array Data.Vector.empty - -- | Determine if an array is empty. -- -- > isEmpty empty == True isEmpty :: Array a -> Bool isEmpty = unwrap .> Data.Vector.null - -- | Return the length of an array. -- -- > length (fromLinkedList [1,2,3]) == 3 @@ -88,7 +89,6 @@ length = .> Data.Vector.length .> Prelude.fromIntegral - -- | Initialize an array. @initialize n f@ creates an array of length @n@ with -- the element at index @i@ initialized to the result of @(f i)@. -- @@ -102,7 +102,6 @@ initialize n f = (Prelude.fromIntegral n) (Prelude.fromIntegral .> f) - -- | Creates an array with a given length, filled with a default element. -- -- > repeat 5 0 == fromLinkedList [0,0,0,0,0] @@ -114,13 +113,11 @@ repeat n element = Array <| Data.Vector.replicate (Prelude.fromIntegral n) element - -- | Create an array from a 'LinkedList'. fromLinkedList :: LinkedList a -> Array a fromLinkedList = Data.Vector.fromList .> Array - -- | Return @Just@ the element at the index or @Nothing@ if the index is out of range. -- -- > get 0 (fromLinkedList [0,1,2]) == Just 0 @@ -131,7 +128,6 @@ get :: Int -> Array a -> Maybe a get i array = unwrap array !? Prelude.fromIntegral i - -- | Set the element at a particular index. Returns an updated array. -- -- If the index is out of range, the array is unaltered. @@ -139,13 +135,12 @@ get i array = -- > set 1 7 (fromLinkedList [1,2,3]) == fromLinkedList [1,7,3] set :: Int -> a -> Array a -> Array a set i value array = Array result - where - len = length array - vector = unwrap array - result - | 0 <= i && i < len = vector Data.Vector.// [(Prelude.fromIntegral i, value)] - | otherwise = vector - + where + len = length array + vector = unwrap array + result + | 0 <= i && i < len = vector Data.Vector.// [(Prelude.fromIntegral i, value)] + | otherwise = vector -- | Push an element onto the end of an array. -- @@ -154,14 +149,12 @@ push :: a -> Array a -> Array a push a (Array vector) = Array (Data.Vector.snoc vector a) - -- | Create a list of elements from an array. -- -- > toLinkedList (fromLinkedList [3,5,8]) == [3,5,8] toLinkedList :: Array a -> LinkedList a toLinkedList = unwrap .> Data.Vector.toList - -- | Create an indexed list from an array. Each element of the array will be -- paired with its index. -- @@ -173,14 +166,12 @@ toIndexedLinkedList = .> Data.Vector.toList .> LinkedList.map (Tuple.mapFirst Prelude.fromIntegral) - -- | Reduce an array from the right. Read @foldr@ as fold from the right. -- -- > foldr (+) 0 (repeat 3 5) == 15 foldr :: (a -> b -> b) -> b -> Array a -> b foldr f value array = Prelude.foldr f value (unwrap array) - -- | Reduce an array from the left. Read @foldl@ as fold from the left. -- -- > foldl (:) [] (fromLinkedList [1,2,3]) == [3,2,1] @@ -188,7 +179,6 @@ foldl :: (a -> b -> b) -> b -> Array a -> b foldl f value array = Data.Foldable.foldl' (\a b -> f b a) value (unwrap array) - -- | Keep elements that pass the test. -- -- > takeIf isEven (fromLinkedList [1,2,3,4,5,6]) == (fromLinkedList [2,4,6]) @@ -196,7 +186,6 @@ takeIf :: (a -> Bool) -> Array a -> Array a takeIf f (Array vector) = Array (Data.Vector.filter f vector) - -- | Apply a function on every element in an array. -- -- > map sqrt (fromLinkedList [1,4,9]) == fromLinkedList [1,2,3] @@ -204,7 +193,6 @@ map :: (a -> b) -> Array a -> Array b map f (Array vector) = Array (Data.Vector.map f vector) - -- | Apply a function on every element with its index as first argument. -- -- > indexedMap (*) (fromLinkedList [5,5,5]) == fromLinkedList [0,5,10] @@ -212,7 +200,6 @@ indexedMap :: (Int -> a -> b) -> Array a -> Array b indexedMap f (Array vector) = Array (Data.Vector.imap (Prelude.fromIntegral .> f) vector) - -- | Append two arrays to a new one. -- -- > append (repeat 2 42) (repeat 3 81) == fromLinkedList [42,42,81,81,81] @@ -220,7 +207,6 @@ append :: Array a -> Array a -> Array a append (Array first) (Array second) = Array (first ++ second) - -- | Get a sub-section of an array: @(slice start end array)@. The @start@ is a -- zero-based index where we will start our slice. The @end@ is a zero-based index -- that indicates the end of the slice. The slice extracts up to but not including @@ -241,19 +227,18 @@ slice :: Int -> Int -> Array a -> Array a slice from to (Array vector) | sliceLen <= 0 = empty | otherwise = Array <| Data.Vector.slice from' sliceLen vector - where - len = Data.Vector.length vector - handleNegative value - | value < 0 = len + value - | otherwise = value - normalize = - Prelude.fromIntegral - .> handleNegative - .> clamp 0 len - from' = normalize from - to' = normalize to - sliceLen = to' - from' - + where + len = Data.Vector.length vector + handleNegative value + | value < 0 = len + value + | otherwise = value + normalize = + Prelude.fromIntegral + .> handleNegative + .> clamp 0 len + from' = normalize from + to' = normalize to + sliceLen = to' - from' -- | Applies a function to each element of an array and flattens the resulting arrays into a single array. -- @@ -266,33 +251,7 @@ slice from to (Array vector) -- element of `array` using the `map` function. Then, it flattens the resulting arrays into a single array -- using the `foldr` function with the `append` function as the folding operation and `empty` as the initial -- value. The resulting array is then returned. --- --- The `flatMap` function has the following properties: --- --- prop> flatMap f empty == empty --- prop> flatMap f (singleton x) == f x --- prop> flatMap f (fromLinkedList xs) == concatMap f xs --- --- where `empty` is the empty array, `singleton` creates an array with a single element, and `fromLinkedList` creates --- an array from a list of elements. --- --- Examples: --- --- >>> let array = fromLinkedList [1, 2, 3] --- >>> let f x = fromLinkedList [x, x + 1] --- >>> flatMap f array --- [1, 2, 2, 3, 3, 4] --- --- >>> let array = fromLinkedList ["Hello", "World"] --- >>> let f x = fromLinkedList (words x) --- >>> flatMap f array --- ["Hello", "World"] --- --- >>> let array = fromLinkedList [1, 2, 3] --- >>> let f x = fromLinkedList [x * 2] --- >>> flatMap f array --- [2, 4, 6] --- + -- This function is commonly used in functional programming to apply a function to each element of a nested -- data structure and flatten the resulting structure into a single level. flatMap :: @@ -307,15 +266,7 @@ flatMap f array = |> map f |> foldr append empty - -- | Emulates a foreach-loop like in other languages. --- --- Example: --- --- >>> forEach [1, 2, 3] (\x -> putStrLn (show x)) --- 1 --- 2 --- 3 forEach :: forall (element :: Type). (element -> IO ()) -> Array element -> IO () forEach callback self = Data.Foldable.traverse_ callback (unwrap self) diff --git a/core/core/Maybe.hs b/core/core/Maybe.hs index 23b8b8e..ec26f82 100644 --- a/core/core/Maybe.hs +++ b/core/core/Maybe.hs @@ -1,34 +1,27 @@ -- | This library fills a bunch of important niches in Elm. A Maybe can help you with optional arguments, error handling, and records with optional fields. -module Maybe ( - -- * Definition - Maybe (..), +module Maybe + ( -- * Definition + Maybe (..), - -- * Common Helpers - withDefault, - map, + -- * Common Helpers + withDefault, + map, - -- * Chaining Maybes - andThen, - getOrDie, -) where + -- * Chaining Maybes + andThen, + getOrDie, + ) +where import Basics import Data.Maybe (Maybe (..), fromMaybe) import Mappable qualified import Thenable qualified - -- | Provide a default value, turning an optional value into a normal -- value. This comes in handy when paired with functions like -- 'Dict.get' which gives back a @Maybe@. -- --- > >>> withDefault 100 (Just 42) --- > 42 --- > >>> withDefault 100 Nothing --- > 100 --- > >>> withDefault "unknown" (Dict.get "Tom" Dict.empty) --- > "unknown" --- -- __Note:__ This can be overused! Many cases are better handled by a @case@ -- expression. And if you end up using @withDefault@ a lot, it can be a good sign -- that a [custom type](https://guide.elm-lang.org/types/custom_types.html) @@ -37,22 +30,11 @@ withDefault :: a -> Maybe a -> a withDefault = Data.Maybe.fromMaybe - --- | Transform a @Maybe@ value with a given function: --- --- > >>> map sqrt (Just 9) --- > Just 3 --- > >>> map sqrt Nothing --- > Nothing --- > >>> map sqrt (String.toFloat "9") --- > Just 3 --- > >>> map sqrt (String.toFloat "x") --- > Nothing +-- | Transform a @Maybe@ value with a function: map :: (a -> b) -> Maybe a -> Maybe b map = Mappable.map - -- | Chain together many computations that may fail. It is helpful to see an -- equivalent definition: -- @@ -89,7 +71,6 @@ andThen :: (a -> Maybe b) -> Maybe a -> Maybe b andThen = Thenable.andThen - -- | Attempts to retrieve the value from a @Maybe@. If the @Maybe@ is @Nothing@, -- the application will crash abruptly. getOrDie :: Maybe a -> a @@ -99,4 +80,4 @@ getOrDie maybe = value Nothing -> dieWith "Maybe.getOrDie: Got Nothing" -{-# INLINE getOrDie #-} \ No newline at end of file +{-# INLINE getOrDie #-} diff --git a/core/core/Unknown.hs b/core/core/Unknown.hs index cf771ac..4455bc2 100644 --- a/core/core/Unknown.hs +++ b/core/core/Unknown.hs @@ -27,25 +27,11 @@ newtype Unknown = Unknown Data.Dynamic.Dynamic type Convertible value = Typeable value -- | Convert a value of any type to 'Unknown'. --- --- >>> fromValue 42 --- Unknown (Data.Dynamic.toDyn 42) --- --- >>> fromValue "hello" --- Unknown (Data.Dynamic.toDyn "hello") fromValue :: (Typeable value) => value -> Unknown fromValue value = Unknown (Data.Dynamic.toDyn value) -- | Convert an 'Unknown' value back to its original type, if possible. --- --- >>> let unknown = fromValue 42 --- >>> toValue unknown :: Maybe Int --- Just 42 --- --- >>> let unknown = fromValue "hello" --- >>> toValue unknown :: Maybe Int --- Nothing toValue :: (Typeable value) => Unknown -> Maybe value toValue (Unknown dynamic) = Data.Dynamic.fromDynamic dynamic diff --git a/core/nhcore.cabal b/core/nhcore.cabal index 8772b23..4dc34d8 100644 --- a/core/nhcore.cabal +++ b/core/nhcore.cabal @@ -37,6 +37,8 @@ common common_cfg large-anon, pretty-simple, unagi-chan, + QuickCheck, + quickcheck-instances, template-haskell default-extensions: ApplicativeDo diff --git a/core/test/Main.hs b/core/test/Main.hs index 3e2059e..dadc637 100644 --- a/core/test/Main.hs +++ b/core/test/Main.hs @@ -1,4 +1,6 @@ module Main (main) where +import Core + main :: IO () -main = putStrLn "Test suite not yet implemented." +main = print "Test suite not yet implemented." diff --git a/devenv.lock b/devenv.lock index d50b3bd..fd87aba 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,11 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1720853497, + "lastModified": 1722019186, "owner": "cachix", "repo": "devenv", - "rev": "7f569a0f2473b9f6000fd9e4c32511fd1b0d37c1", - "treeHash": "4d452ecc8223834e39d507f9ea92308f007ee05d", + "rev": "075c114280751f956335333179304d14ae01aedc", + "treeHash": "0e550d934a0d3f6248accff4c019c013e8e237e6", "type": "github" }, "original": { @@ -88,11 +88,11 @@ }, "nixpkgs-stable_2": { "locked": { - "lastModified": 1720954236, + "lastModified": 1722087241, "owner": "NixOS", "repo": "nixpkgs", - "rev": "53e81e790209e41f0c1efa9ff26ff2fd7ab35e27", - "treeHash": "ca1f1273cf201da604f7c704535d4b7fac62cdb2", + "rev": "8c50662509100d53229d4be607f1a3a31157fa12", + "treeHash": "cbc560aaf05dfc49bde55e55f603d7245515b13a", "type": "github" }, "original": { diff --git a/devenv.nix b/devenv.nix index 7f5cd3f..8ce68ca 100644 --- a/devenv.nix +++ b/devenv.nix @@ -9,10 +9,23 @@ git ghcid haskellPackages.implicit-hie + haskellPackages.doctest ]; # https://devenv.sh/scripts/ - scripts.watch.exec = "ghcid --command=cabal repl $1"; + scripts = { + run-watch.exec = "ghcid --command=cabal repl $1"; + run-build.exec = "cabal build all"; + run-update.exec = "cabal update"; + run-cli.exec = "cabal run nhcli -- $@"; + run-core-tests.exec = '' + cabal repl nhcore --with-compiler=doctest && \ + cabal test nhcore + ''; + run-cli-tests.exec = '' + cabal test nhcli + ''; + }; enterShell = '' gen-hie > hie.yaml