diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d624d2e..4dc2d20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,4 +24,7 @@ jobs: run: if [ -f scripts/test ]; then nix develop -c bash ./scripts/test; fi - name: Luacheck - run: nix develop -c luacheck --quiet --std lua51 --no-unused-args src/ + run: nix develop -c luacheck --quiet --std lua51 --no-unused-args --max-line-length 130 src/ + + - name: Format check + run: nix fmt && git diff --exit-code diff --git a/.gitignore b/.gitignore index db67e9a..e070528 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /.* !/.gitignore !/.github/ +!/.tidyrc.json +!/.lua-format /output/ diff --git a/.lua-format b/.lua-format new file mode 100644 index 0000000..2945014 --- /dev/null +++ b/.lua-format @@ -0,0 +1,10 @@ +# LuaFormatter config for the hand-written FFI under src/. +# 2-space indent. Keep simple functions on one line; column_limit sits a few +# columns under luacheck's 130 limit because lua-format under-counts the leading +# indent and trailing comma, so this keeps every emitted line within 130. +indent_width: 2 +use_tab: false +column_limit: 126 +continuation_indent_width: 2 +keep_simple_function_one_line: true +keep_simple_control_block_one_line: true diff --git a/.tidyrc.json b/.tidyrc.json new file mode 100644 index 0000000..8636af8 --- /dev/null +++ b/.tidyrc.json @@ -0,0 +1,10 @@ +{ + "importSort": "source", + "importWrap": "source", + "indent": 2, + "operatorsFile": null, + "ribbon": 1, + "typeArrowPlacement": "first", + "unicode": "source", + "width": 80 +} diff --git a/AGENTS.md b/AGENTS.md index dde531a..a312901 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,11 +4,21 @@ A PureScript→Lua FFI fork in the [`purescript-lua`](https://github.com/purescr ## Commands -All commands run inside the nix dev shell: - - Build: `nix develop -c ./scripts/build` - Test (only if the fork has `scripts/test`): `nix develop -c bash ./scripts/test` -- Lint: `nix develop -c luacheck --quiet --std lua51 --no-unused-args src/` +- Lint: `nix develop -c luacheck --quiet --std lua51 --no-unused-args --max-line-length 130 src/` +- Format: `nix fmt` (check: `nix fmt && git diff --exit-code`) + +## Formatting + +`nix fmt` runs treefmt (`treefmt.nix`): nixfmt for Nix, `dhall format`, purs-tidy +for `*.purs` (config in `.tidyrc.json`), and LuaFormatter for the `*.lua` FFI +(config in `.lua-format`). LuaFormatter is used over StyLua because it keeps the +parentheses pslua's foreign-file parser requires. The Lua line budget is 130 +columns, matching the `luacheck --max-line-length` above. The check is +content-based (`nix fmt && git diff --exit-code`) rather than `treefmt --ci`, +since the in-place formatters bump mtime even when content is unchanged, which +trips treefmt's `--fail-on-change`. CI and the pre-commit hook use it. ## Lua 5.1 target diff --git a/flake.lock b/flake.lock index 6b6c417..c47b792 100644 --- a/flake.lock +++ b/flake.lock @@ -740,7 +740,8 @@ "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", "pslua": "pslua", - "purescript-overlay": "purescript-overlay" + "purescript-overlay": "purescript-overlay", + "treefmt-nix": "treefmt-nix" } }, "stackage": { @@ -803,6 +804,26 @@ "repo": "default", "type": "github" } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 5198865..4d86c4b 100644 --- a/flake.nix +++ b/flake.nix @@ -9,16 +9,33 @@ inputs.nixpkgs.follows = "nixpkgs"; }; pslua.url = "github:purescript-lua/purescript-lua"; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { self, nixpkgs, flake-utils, purescript-overlay, pslua }: - flake-utils.lib.eachDefaultSystem (system: + outputs = + { + self, + nixpkgs, + flake-utils, + purescript-overlay, + pslua, + treefmt-nix, + }: + flake-utils.lib.eachDefaultSystem ( + system: let pkgs = import nixpkgs { inherit system; overlays = [ purescript-overlay.overlays.default ]; }; - in { + treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix; + in + { + formatter = treefmtEval.config.build.wrapper; + checks.formatting = treefmtEval.config.build.check self; devShell = pkgs.mkShell { buildInputs = with pkgs; [ dhall @@ -31,8 +48,26 @@ spago-bin.spago-0_21_0 treefmt ]; + # Install a content-based pre-commit hook. It compares the working + # tree diff before and after `nix fmt`, so it only objects to changes + # the formatter itself introduces (not the developer's existing + # unstaged work) and is not fooled by formatters that only bump mtime. + # Rewritten each shell entry to stay in sync with this flake. + shellHook = '' + hook=.git/hooks/pre-commit + if [ -d .git ]; then + printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'before=$(git diff)' \ + 'nix fmt >/dev/null 2>&1 || exit 0' \ + '[ "$before" = "$(git diff)" ] || { echo "nix fmt changed files; re-stage them, then commit." >&2; exit 1; }' \ + > "$hook" + chmod +x "$hook" + fi + ''; }; - }); + } + ); # --- Flake Local Nix Configuration ---------------------------- nixConfig = { diff --git a/spago.dhall b/spago.dhall index 171d407..a940a2f 100644 --- a/spago.dhall +++ b/spago.dhall @@ -1,11 +1,6 @@ { name = "purescript-lua-unfoldable" , dependencies = - [ "foldable-traversable" - , "maybe" - , "partial" - , "prelude" - , "tuples" - ] + [ "foldable-traversable", "maybe", "partial", "prelude", "tuples" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs" ] , backend = diff --git a/src/Data/Unfoldable.lua b/src/Data/Unfoldable.lua index 85ce757..444a388 100644 --- a/src/Data/Unfoldable.lua +++ b/src/Data/Unfoldable.lua @@ -9,9 +9,7 @@ return { local value = b while true do local maybe = f(value) - if isNothing(maybe) then - return result - end + if isNothing(maybe) then return result end local tuple = fromJust(maybe) table.insert(result, fst(tuple)) value = snd(tuple) diff --git a/src/Data/Unfoldable.purs b/src/Data/Unfoldable.purs index fd115c1..5ac0364 100644 --- a/src/Data/Unfoldable.purs +++ b/src/Data/Unfoldable.purs @@ -5,7 +5,8 @@ -- | sequences, etc. module Data.Unfoldable - ( class Unfoldable, unfoldr + ( class Unfoldable + , unfoldr , replicate , replicateA , none @@ -63,10 +64,10 @@ foreign import unfoldrArrayImpl replicate :: forall f a. Unfoldable f => Int -> a -> f a replicate n v = unfoldr step n where - step :: Int -> Maybe (Tuple a Int) - step i = - if i <= 0 then Nothing - else Just (Tuple v (i - 1)) + step :: Int -> Maybe (Tuple a Int) + step i = + if i <= 0 then Nothing + else Just (Tuple v (i - 1)) -- | Perform an Applicative action `n` times, and accumulate all the results. -- | diff --git a/src/Data/Unfoldable1.purs b/src/Data/Unfoldable1.purs index 2eb285b..a487569 100644 --- a/src/Data/Unfoldable1.purs +++ b/src/Data/Unfoldable1.purs @@ -1,5 +1,6 @@ module Data.Unfoldable1 - ( class Unfoldable1, unfoldr1 + ( class Unfoldable1 + , unfoldr1 , replicate1 , replicate1A , singleton @@ -65,10 +66,10 @@ foreign import unfoldr1ArrayImpl replicate1 :: forall f a. Unfoldable1 f => Int -> a -> f a replicate1 n v = unfoldr1 step (n - 1) where - step :: Int -> Tuple a (Maybe Int) - step i - | i <= 0 = Tuple v Nothing - | otherwise = Tuple v (Just (i - 1)) + step :: Int -> Tuple a (Maybe Int) + step i + | i <= 0 = Tuple v Nothing + | otherwise = Tuple v (Just (i - 1)) -- | Perform an `Apply` action `n` times (at least once, so values `n` less -- | than 1 will be treated as 1), and accumulate the results. @@ -109,9 +110,11 @@ range :: forall f. Unfoldable1 f => Int -> Int -> f Int range start end = let delta = if end >= start then 1 else -1 in unfoldr1 (go delta) start where - go delta i = - let i' = i + delta - in Tuple i (if i == end then Nothing else Just i') + go delta i = + let + i' = i + delta + in + Tuple i (if i == end then Nothing else Just i') -- | Create an `Unfoldable1` by repeated application of a function to a seed value. -- | For example: diff --git a/treefmt.nix b/treefmt.nix new file mode 100644 index 0000000..0f57573 --- /dev/null +++ b/treefmt.nix @@ -0,0 +1,43 @@ +{ pkgs, ... }: +{ + projectRootFile = "flake.nix"; + + # Nix — RFC 166 formatter. + programs.nixfmt.enable = true; + + # Dhall — spago.dhall / packages.dhall layout. + programs.dhall.enable = true; + + # PureScript — purs-tidy is not a first-class treefmt program, so wire it via + # the generic mechanism. It picks up `.tidyrc.json` from the project root. + settings.formatter.purs-tidy = { + command = "${pkgs.purs-tidy}/bin/purs-tidy"; + options = [ "format-in-place" ]; + includes = [ "*.purs" ]; + }; + + # Lua FFI — LuaFormatter keeps the parentheses pslua's foreign-file parser + # requires (unlike StyLua, which strips them). Config in `.lua-format`. + settings.formatter.lua-format = { + command = "${pkgs.luaformatter}/bin/lua-format"; + options = [ + "-i" + "-c" + ".lua-format" + ]; + includes = [ "*.lua" ]; + }; + + # Never format generated output or vendored trees. + settings.global.excludes = [ + "dist/*" + "output/*" + ".spago/*" + "node_modules/*" + "*.lock" + "flake.lock" + "spago.lock" + ".tidyrc.json" + ".lua-format" + ]; +}