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 +13/−0
- README.md +2/−10
- main/Main.hs +55/−20
- nixfmt.cabal +14/−8
- src/Nixfmt.hs +1/−0
- src/Nixfmt/Parser.hs +3/−2
- src/Nixfmt/Parser/Float.hs +61/−0
- src/Nixfmt/Predoc.hs +1/−0
- src/Nixfmt/Pretty.hs +10/−9
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 -[](https://buildkite.com/serokell/nixfmt)-[](https://nixfmt.cachix.org)+ ## 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 <> "\\$"