packages feed

nixfmt 0.5.0 → 0.6.0

raw patch · 9 files changed

+160/−49 lines, 9 filesdep +scientificdep ~basedep ~megaparsecdep ~textnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: scientific

Dependency ranges changed: base, megaparsec, text, unix

API changes (from Hackage documentation)

+ Nixfmt: type Width = Int
+ Nixfmt.Parser.Float: floatParse :: (MonadParsec e s m, Token s ~ Char, RealFloat a) => m a
+ Nixfmt.Predoc: data DocE

Files

CHANGELOG.md view
@@ -7,7 +7,19 @@  # Revision history for nixfmt +## Unreleased++## 0.6.0 -- 2023-10-31++* Fix escaping of interpolations after dollar signs.+* Fix nixfmt trying to allocate temp files that aren't used.+* Don't write if files didn't change, fixing treefmt compatibility+* Nixfmt now accepts the '-' argument to read from stdin.+* `nixfmt [dir]` now recursively formats nix files in that directory.+* Float and int literal parsing now matches nix.+ ## 0.5.0 -- 2022-03-15+ * Add a nix flake to the nixfmt project. * Add a --verify flag to check idempotency. * Support nix path (`./${foo}.nix`) interpolations.@@ -15,6 +27,7 @@ * Fix handling of multiline strings with spaces in the last line.  ## 0.4.0 -- 2020-02-10+ * Report non-conforming files on the same line to aid line-oriented processing * Fix help, summary, and version flag contents. * Fix indentation of leading comments in parens
README.md view
@@ -10,8 +10,7 @@  You are encouraged to test this out on your code and submit any undesirable formatting you find as an issue -[![Build Status](https://badge.buildkite.com/b37f73adea391439e63288e8fd3b47f4b98fb9640bb864ccfa.svg)](https://buildkite.com/serokell/nixfmt)-[![Cachix](https://img.shields.io/badge/cachix-nixfmt-blue.svg)](https://nixfmt.cachix.org)+![Build Status](https://github.com/serokell/nixfmt/actions/workflows/main.yml/badge.svg?branch=master)  ## Installation @@ -21,8 +20,6 @@  - To get the most recent version, install from master: -      # Optional: use cachix to get cached builds even for master-      cachix use nixfmt       nix-env -f https://github.com/serokell/nixfmt/archive/master.tar.gz -i  - Nix with flakes@@ -47,13 +44,8 @@  ## Usage -* `nixfmt < input.nix` – reads Nix code form `stdin`, formats it, and outputs to `stdout`+* `nixfmt < input.nix` – reads Nix code from `stdin`, formats it, and outputs to `stdout` * `nixfmt file.nix` – format the file in place---## For Contributors--We welcome issues and pull requests on GitHub.   ## About Serokell
main/Main.hs view
@@ -4,22 +4,25 @@  - SPDX-License-Identifier: MPL-2.0  -} -{-# LANGUAGE DeriveDataTypeable, NamedFieldPuns #-}+{-# LANGUAGE DeriveDataTypeable, NamedFieldPuns, MultiWayIf #-}  module Main where  import Control.Concurrent (Chan, forkIO, newChan, readChan, writeChan) import Data.Either (lefts) import Data.Text (Text)+import Data.List (isSuffixOf) import Data.Version (showVersion) import GHC.IO.Encoding (utf8) import Paths_nixfmt (version) import System.Console.CmdArgs   (Data, Typeable, args, cmdArgs, help, summary, typ, (&=)) import System.Exit (ExitCode(..), exitFailure, exitSuccess)+import System.FilePath ((</>)) import System.IO (hPutStrLn, hSetEncoding, stderr) import System.Posix.Process (exitImmediately) import System.Posix.Signals (Handler(..), installHandler, keyboardSignal)+import System.Directory (listDirectory, doesDirectoryExist)  import qualified Data.Text.IO as TextIO (getContents, hPutStr, putStr) @@ -39,26 +42,51 @@     } deriving (Show, Data, Typeable)  options :: Nixfmt-options = Nixfmt-    { files = [] &= args &= typ "FILES"-    , width = 80 &= help "Maximum width in characters"-    , check = False &= help "Check whether files are formatted"-    , quiet = False &= help "Do not report errors"-    , verify = False &= help "Check that the output parses and formats the same as the input"-    } &= summary ("nixfmt v" ++ showVersion version)-    &= help "Format Nix source code"+options =+  let defaultWidth = 80+      addDefaultHint value message =+        message ++ "\n[default: " ++ show value ++ "]"+   in Nixfmt+        { files = [] &= args &= typ "FILES"+        , width =+            defaultWidth &=+            help (addDefaultHint defaultWidth "Maximum width in characters")+        , check = False &= help "Check whether files are formatted"+        , quiet = False &= help "Do not report errors"+        , verify =+            False &=+            help+              "Check that the output parses and formats the same as the input"+        } &=+      summary ("nixfmt v" ++ showVersion version) &=+      help "Format Nix source code"  data Target = Target     { tDoRead :: IO Text     , tPath :: FilePath-    , tDoWrite :: Text -> IO ()+      -- The bool is true when the formatted file differs from the input+    , tDoWrite :: Bool -> Text -> IO ()     } +-- | Recursively collect nix files in a directory+collectNixFiles :: FilePath -> IO [FilePath]+collectNixFiles path = do+  dir <- doesDirectoryExist path+  if | dir -> do+         files <- listDirectory path+         concat <$> mapM collectNixFiles ((path </>) <$> files)+     | ".nix" `isSuffixOf` path -> pure [path]+     | otherwise -> pure []++-- | Recursively collect nix files in a list of directories+collectAllNixFiles :: [FilePath] -> IO [FilePath]+collectAllNixFiles paths = concat <$> mapM collectNixFiles paths+ formatTarget :: Formatter -> Target -> IO Result formatTarget format Target{tDoRead, tPath, tDoWrite} = do     contents <- tDoRead-    let formatted = format tPath contents-    mapM tDoWrite formatted+    let formatResult = format tPath contents+    mapM (\formatted -> tDoWrite (formatted /= contents) formatted) formatResult  -- | Return an error if target could not be parsed or was not formatted -- correctly.@@ -72,19 +100,26 @@             | otherwise             -> Left $ tPath ++ ": not formatted"  stdioTarget :: Target-stdioTarget = Target TextIO.getContents "<stdin>" TextIO.putStr+stdioTarget = Target TextIO.getContents "<stdin>" (const $ TextIO.putStr)  fileTarget :: FilePath -> Target fileTarget path = Target (readFileUtf8 path) path atomicWriteFile   where-    atomicWriteFile t = withOutputFile path $ \h -> do+    atomicWriteFile True t = withOutputFile path $ \h -> do       hSetEncoding h utf8       TextIO.hPutStr h t+    -- Don't do anything if the file is already formatted+    atomicWriteFile False _ = mempty -toTargets :: Nixfmt -> [Target]-toTargets Nixfmt{ files = [] }    = [stdioTarget]-toTargets Nixfmt{ files = paths } = map fileTarget paths+checkFileTarget :: FilePath -> Target+checkFileTarget path = Target (readFileUtf8 path) path (const $ const $ pure ()) +toTargets :: Nixfmt -> IO [Target]+toTargets Nixfmt{ files = [] }    = pure [stdioTarget]+toTargets Nixfmt{ files = ["-"] } = pure [stdioTarget]+toTargets Nixfmt{ check = False, files = paths } = map fileTarget <$> collectAllNixFiles paths+toTargets Nixfmt{ check = True, files = paths } = map checkFileTarget <$> collectAllNixFiles paths+ type Formatter = FilePath -> Text -> Either String Text  toFormatter :: Nixfmt -> Formatter@@ -101,8 +136,8 @@ toWriteError Nixfmt{ quiet = False } = hPutStrLn stderr toWriteError Nixfmt{ quiet = True } = const $ return () -toJobs :: Nixfmt -> [IO Result]-toJobs opts = map (toOperation opts $ toFormatter opts) $ toTargets opts+toJobs :: Nixfmt -> IO [IO Result]+toJobs opts = map (toOperation opts $ toFormatter opts) <$> toTargets opts  -- TODO: Efficient parallel implementation. This is just a sequential stub. -- This was originally implemented using parallel-io, but it gave a factor two@@ -141,7 +176,7 @@     _ <- installHandler keyboardSignal             (Catch (exitImmediately $ ExitFailure 2)) Nothing     opts <- cmdArgs options-    results <- runJobs (toWriteError opts) (toJobs opts)+    results <- runJobs (toWriteError opts) =<< toJobs opts     case lefts results of          [] -> exitSuccess          _  -> exitFailure
nixfmt.cabal view
@@ -6,7 +6,7 @@ -- SPDX-License-Identifier: MPL-2.0  name:                nixfmt-version:             0.5.0+version:             0.6.0 synopsis:            An opinionated formatter for Nix description:   A formatter for Nix that ensures consistent and clear formatting by forgetting@@ -41,11 +41,11 @@   else     buildable: True   build-depends:-      base             >= 4.12.0 && < 4.17+      base             >= 4.12.0 && < 4.19     , cmdargs          >= 0.10.20 && < 0.11     , nixfmt-    , unix             >= 2.7.2 && < 2.8-    , text             >= 1.2.3 && < 1.3+    , unix             >= 2.7.2 && < 2.9+    , text             >= 1.2.3 && < 2.2      -- for System.IO.Atomic     , directory        >= 1.3.3 && < 1.4@@ -65,26 +65,32 @@     Nixfmt     Nixfmt.Lexer     Nixfmt.Parser+    Nixfmt.Parser.Float     Nixfmt.Predoc     Nixfmt.Pretty     Nixfmt.Types     Nixfmt.Util +  default-extensions:+    PackageImports+   other-extensions:     DeriveFoldable     DeriveFunctor     FlexibleInstances     LambdaCase     OverloadedStrings+    ScopedTypeVariables     StandaloneDeriving     TupleSections    hs-source-dirs:      src   build-depends:-      base             >= 4.12.0 && < 4.17-    , megaparsec       >= 9.0.1 && < 9.3+      base             >= 4.12.0 && < 4.19+    , megaparsec       >= 9.0.1 && < 9.6     , parser-combinators >= 1.0.3 && < 1.4-    , text             >= 1.2.3 && < 1.3+    , scientific       >= 0.3.0 && < 0.4.0+    , text             >= 1.2.3 && < 2.2   default-language:    Haskell2010   ghc-options:     -Wall@@ -107,7 +113,7 @@       -Wredundant-constraints       -Wno-orphans     build-depends:-      base             >= 4.12.0 && < 4.17+      base             >= 4.12.0 && < 4.19       , ghcjs-base     >= 0.2.0 && < 0.3       , nixfmt     hs-source-dirs:    js/
src/Nixfmt.hs view
@@ -7,6 +7,7 @@ module Nixfmt     ( errorBundlePretty     , ParseErrorBundle+    , Width     , format     , formatVerify     ) where
src/Nixfmt/Parser.hs view
@@ -22,13 +22,14 @@   (anySingle, chunk, eof, label, lookAhead, many, notFollowedBy, oneOf,   optional, satisfy, some, try, (<|>)) import Text.Megaparsec.Char (char)-import qualified Text.Megaparsec.Char.Lexer as L (decimal, float)+import qualified Text.Megaparsec.Char.Lexer as L (decimal)  import Nixfmt.Lexer (lexeme) import Nixfmt.Types   (Ann, Binder(..), Expression(..), File(..), Fixity(..), Leaf, Operator(..),   ParamAttr(..), Parameter(..), Parser, Path, Selector(..), SimpleSelector(..),   String, StringPart(..), Term(..), Token(..), operators, tokenText)+import Nixfmt.Parser.Float (floatParse) import Nixfmt.Util   (commonIndentation, identChar, isSpaces, manyP, manyText, pathChar,   schemeChar, someP, someText, uriChar)@@ -65,7 +66,7 @@ integer = ann Integer L.decimal  float :: Parser (Ann Token)-float = ann Float L.float+float = ann Float floatParse  identifier :: Parser (Ann Token) identifier = ann Identifier $ do
+ src/Nixfmt/Parser/Float.hs view
@@ -0,0 +1,61 @@+{- © 2022 Serokell <hi@serokell.io>+ - © 2022 Lars Jellema <lars.jellema@gmail.com>+ -+ - SPDX-License-Identifier: MPL-2.0+ -}++{-# LANGUAGE TypeFamilies, TypeApplications, ScopedTypeVariables, TypeOperators #-}++module Nixfmt.Parser.Float (floatParse) where++import "base" Data.Foldable (foldl')+import "base" Data.Proxy (Proxy (..))+import qualified "base" Data.Char as Char++import "base" Control.Monad (void)+import "megaparsec" Text.Megaparsec (+    option, chunkToTokens, takeWhile1P, try, notFollowedBy,+    (<|>), (<?>), MonadParsec, Token,+  )+import "megaparsec" Text.Megaparsec.Char.Lexer (decimal, signed)+import "megaparsec" Text.Megaparsec.Char (char, char', digitChar)++import "scientific" Data.Scientific (toRealFloat, scientific)++-- copied (and modified) from Text.Megaparsec.Char.Lexer+data SP = SP !Integer {-# UNPACK #-} !Int+floatParse :: (MonadParsec e s m, Token s ~ Char, RealFloat a) => m a+floatParse = do+  notFollowedBy $ (char '0') >> digitChar+  notFollowedBy $ (char' 'e')+  c' <- (decimal <?> "decimal") <|> return 0+  toRealFloat+    <$> (( do+              SP c e' <- dotDecimal_ c'+              e <- option e' (try $ exponent_ e')+              return (scientific c e)+         )+            <|> (scientific c' <$> exponent_ 0)+        )+{-# INLINE floatParse #-}++-- copied from Text.Megaparsec.Char.Lexer+dotDecimal_ :: forall e s m.+  (MonadParsec e s m, Token s ~ Char) => Integer -> m SP+dotDecimal_ c' = do+  void (char '.')+  let mkNum = foldl' step (SP c' 0) . chunkToTokens @s Proxy+      step (SP a e') c =+        SP+          (a * 10 + fromIntegral (Char.digitToInt c))+          (e' - 1)+  mkNum <$> takeWhile1P (Just "digit") Char.isDigit+{-# INLINE dotDecimal_ #-}++-- copied from Text.Megaparsec.Char.Lexer+exponent_ :: (MonadParsec e s m, Token s ~ Char) => Int -> m Int+exponent_ e' = do+  void (char' 'e')+  (+ e') <$> signed (return ()) decimal+{-# INLINE exponent_ #-}+
src/Nixfmt/Predoc.hs view
@@ -24,6 +24,7 @@     , hardline     , emptyline     , newline+    , DocE     , Doc     , Pretty     , pretty
src/Nixfmt/Pretty.hs view
@@ -4,7 +4,7 @@  - SPDX-License-Identifier: MPL-2.0  -} -{-# LANGUAGE FlexibleInstances, LambdaCase, OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances, OverloadedStrings #-}  module Nixfmt.Pretty where @@ -14,7 +14,7 @@ import Data.Maybe (fromMaybe) import Data.Text (Text, isPrefixOf, isSuffixOf, stripPrefix) import qualified Data.Text as Text-  (dropEnd, empty, init, isInfixOf, last, null, replace, strip, takeWhile)+  (dropEnd, empty, init, isInfixOf, last, null, strip, takeWhile)  import Nixfmt.Predoc   (Doc, Pretty, base, emptyline, group, hardline, hardspace, hcat, line, line',@@ -377,14 +377,15 @@ prettySimpleString :: [[StringPart]] -> Doc prettySimpleString parts = group $     text "\""-    <> (sepBy (text "\\n") (map (prettyLine escape unescapeInterpol) parts))+    <> sepBy (text "\\n") (map (prettyLine escape unescapeInterpol) parts)     <> text "\""-    where escape-              = Text.replace "$\\${" "$${"-              . Text.replace "${" "\\${"-              . Text.replace "\"" "\\\""-              . Text.replace "\r" "\\r"-              . Text.replace "\\" "\\\\"+    where escape = replaceMultiple+              [ ("$\\${", "$${")+              , ("${",    "\\${")+              , ("\"",    "\\\"")+              , ("\r",    "\\r")+              , ("\\",    "\\\\")+              ]            unescapeInterpol t               | "$" `isSuffixOf` t = Text.init t <> "\\$"