packages feed

nixfmt 0.4.0 → 0.5.0

raw patch · 12 files changed

+203/−109 lines, 12 filesdep ~basedep ~megaparsecdep ~parser-combinatorsPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, megaparsec, parser-combinators

API changes (from Hackage documentation)

- Nixfmt.Parser: dropEmptyParts :: [[StringPart]] -> [[StringPart]]
- Nixfmt.Parser: stripFirstLine :: [[StringPart]] -> [[StringPart]]
- Nixfmt.Parser: textHeads :: [StringPart] -> [Text]
- Nixfmt.Pretty: isEmptyLine :: [StringPart] -> Bool
+ Nixfmt: formatVerify :: Width -> FilePath -> Text -> Either String Text
+ Nixfmt.Parser: fixFirstLine :: [[StringPart]] -> [[StringPart]]
+ Nixfmt.Parser: fixLastLine :: [[StringPart]] -> [[StringPart]]
+ Nixfmt.Parser: lineHead :: [StringPart] -> Maybe Text
+ Nixfmt.Parser: normalizeLine :: [StringPart] -> [StringPart]
+ Nixfmt.Parser: pathText :: Parser StringPart
+ Nixfmt.Parser: pathTraversal :: Parser [StringPart]
+ Nixfmt.Pretty: isInvisibleLine :: [StringPart] -> Bool
+ Nixfmt.Pretty: lastLineIsSpaces :: [[StringPart]] -> Bool
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Binder
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Expression
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.File
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Fixity
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Operator
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.ParamAttr
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Parameter
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Selector
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.SimpleSelector
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.StringPart
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Term
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.TrailingComment
+ Nixfmt.Types: instance GHC.Classes.Eq Nixfmt.Types.Trivium
+ Nixfmt.Types: instance GHC.Classes.Eq a => GHC.Classes.Eq (Nixfmt.Types.Ann a)
+ Nixfmt.Types: type Path = Ann [StringPart]
+ Nixfmt.Util: isSpaces :: Text -> Bool
+ Nixfmt.Util: replaceMultiple :: [(Text, Text)] -> Text -> Text
- Nixfmt: errorBundlePretty :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> String
+ Nixfmt: errorBundlePretty :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> String
- Nixfmt: format :: Int -> FilePath -> Text -> Either ParseErrorBundle Text
+ Nixfmt: format :: Width -> FilePath -> Text -> Either String Text
- Nixfmt.Parser: path :: Parser (Ann Token)
+ Nixfmt.Parser: path :: Parser Path
- Nixfmt.Types: Path :: Text -> Token
+ Nixfmt.Types: Path :: Path -> Term

Files

CHANGELOG.md view
@@ -7,6 +7,13 @@  # Revision history for nixfmt +## 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.+* Fix escaping of interpolations after single quotes.+* 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.
README.md view
@@ -25,6 +25,10 @@       cachix use nixfmt       nix-env -f https://github.com/serokell/nixfmt/archive/master.tar.gz -i +- Nix with flakes++      nix profile install github:serokell/nixfmt+ ## Development  ### With Nix
js/JSInterface.hs view
@@ -29,7 +29,7 @@         out <- case format width filename text of           Left err -> do             setProp "err" (toJSBool True) obj-            toJSVal $ S.pack $ errorBundlePretty err+            toJSVal $ S.pack err           Right out_ -> do             setProp "err" (toJSBool False) obj             toJSVal out_
− js/js-interface-wrapper.js
@@ -1,10 +0,0 @@-/* © 2019 Serokell <hi@serokell.io>- *- * SPDX-License-Identifier: MPL-2.0- */--function nixfmt(text, width=80, filename="<stdin>") {-    const param = {width, filename}-    nixfmt_(text, param)-    return {text: param.ret, err: param.err}-}
main/Main.hs view
@@ -9,7 +9,6 @@ module Main where  import Control.Concurrent (Chan, forkIO, newChan, readChan, writeChan)-import Data.Bifunctor (first) import Data.Either (lefts) import Data.Text (Text) import Data.Version (showVersion)@@ -24,7 +23,7 @@  import qualified Data.Text.IO as TextIO (getContents, hPutStr, putStr) -import Nixfmt+import qualified Nixfmt import System.IO.Atomic (withOutputFile) import System.IO.Utf8 (readFileUtf8, withUtf8StdHandles) @@ -36,6 +35,7 @@     , width :: Width     , check :: Bool     , quiet :: Bool+    , verify :: Bool     } deriving (Show, Data, Typeable)  options :: Nixfmt@@ -44,30 +44,28 @@     , 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" -format' :: Width -> FilePath -> Text -> Either String Text-format' w path = first errorBundlePretty . format w path- data Target = Target     { tDoRead :: IO Text     , tPath :: FilePath     , tDoWrite :: Text -> IO ()     } -formatTarget :: Width -> Target -> IO Result-formatTarget w Target{tDoRead, tPath, tDoWrite} = do+formatTarget :: Formatter -> Target -> IO Result+formatTarget format Target{tDoRead, tPath, tDoWrite} = do     contents <- tDoRead-    let formatted = format' w tPath contents+    let formatted = format tPath contents     mapM tDoWrite formatted  -- | Return an error if target could not be parsed or was not formatted -- correctly.-checkTarget :: Width -> Target -> IO Result-checkTarget w Target{tDoRead, tPath} = do+checkTarget :: Formatter -> Target -> IO Result+checkTarget format Target{tDoRead, tPath} = do     contents <- tDoRead-    return $ case format' w tPath contents of+    return $ case format tPath contents of         Left err -> Left err         Right formatted             | formatted == contents -> Right ()@@ -87,16 +85,24 @@ toTargets Nixfmt{ files = [] }    = [stdioTarget] toTargets Nixfmt{ files = paths } = map fileTarget paths -toOperation :: Nixfmt -> Target -> IO Result-toOperation Nixfmt{ width = w, check = True } = checkTarget w-toOperation Nixfmt{ width = w } = formatTarget w+type Formatter = FilePath -> Text -> Either String Text +toFormatter :: Nixfmt -> Formatter+toFormatter Nixfmt{ width, verify = True  } = Nixfmt.formatVerify width+toFormatter Nixfmt{ width, verify = False } = Nixfmt.format width++type Operation = Formatter -> Target -> IO Result++toOperation :: Nixfmt -> Operation+toOperation Nixfmt{ check = True } = checkTarget+toOperation Nixfmt{ } = formatTarget+ toWriteError :: Nixfmt -> String -> IO () toWriteError Nixfmt{ quiet = False } = hPutStrLn stderr toWriteError Nixfmt{ quiet = True } = const $ return ()  toJobs :: Nixfmt -> [IO Result]-toJobs opts = map (toOperation opts) $ toTargets opts+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
main/System/IO/Utf8.hs view
@@ -10,7 +10,7 @@ -- -- Standard IO functions assume that the character encoding of the data -- they read or write is the same as the one used by current locale. In many--- situtations this assumption is wrong, as tools work with files, and+-- situations this assumption is wrong, as tools work with files, and -- the files nowadays are mostly UTF-8 encoded, regardless of the locale. -- Therefore, it is almost always a good idea to switch the encoding of -- file handles to UTF-8.
nixfmt.cabal view
@@ -1,12 +1,12 @@ cabal-version:       2.0 --- © 2019 Serokell <hi@serokell.io>--- © 2019 Lars Jellema <lars.jellema@gmail.com>+-- © 2022 Serokell <hi@serokell.io>+-- © 2022 Lars Jellema <lars.jellema@gmail.com> -- -- SPDX-License-Identifier: MPL-2.0  name:                nixfmt-version:             0.4.0+version:             0.5.0 synopsis:            An opinionated formatter for Nix description:   A formatter for Nix that ensures consistent and clear formatting by forgetting@@ -17,7 +17,7 @@ license-file:        LICENSE author:              Lars Jellema maintainer:          lars.jellema@gmail.com-copyright:           2019 Serokell, 2019 Lars Jellema+copyright:           2022 Serokell, 2022 Lars Jellema category:            Development build-type:          Simple extra-source-files:  README.md, CHANGELOG.md@@ -41,7 +41,7 @@   else     buildable: True   build-depends:-      base             >= 4.12.0 && < 4.13+      base             >= 4.12.0 && < 4.17     , cmdargs          >= 0.10.20 && < 0.11     , nixfmt     , unix             >= 2.7.2 && < 2.8@@ -69,12 +69,21 @@     Nixfmt.Pretty     Nixfmt.Types     Nixfmt.Util-  other-extensions:    OverloadedStrings, LambdaCase, FlexibleInstances, DeriveFoldable, DeriveFunctor, StandaloneDeriving++  other-extensions:+    DeriveFoldable+    DeriveFunctor+    FlexibleInstances+    LambdaCase+    OverloadedStrings+    StandaloneDeriving+    TupleSections+   hs-source-dirs:      src   build-depends:-      base             >= 4.12.0 && < 4.13-    , megaparsec       >= 7.0.5 && < 7.1-    , parser-combinators >= 1.0.3 && < 1.3+      base             >= 4.12.0 && < 4.17+    , megaparsec       >= 9.0.1 && < 9.3+    , parser-combinators >= 1.0.3 && < 1.4     , text             >= 1.2.3 && < 1.3   default-language:    Haskell2010   ghc-options:@@ -98,10 +107,9 @@       -Wredundant-constraints       -Wno-orphans     build-depends:-        base           >= 4.12.0 && < 4.13+      base             >= 4.12.0 && < 4.17       , ghcjs-base     >= 0.2.0 && < 0.3       , nixfmt-    js-sources:        js/js-interface-wrapper.js     hs-source-dirs:    js/   else     buildable: False
src/Nixfmt.hs view
@@ -8,10 +8,12 @@     ( errorBundlePretty     , ParseErrorBundle     , format+    , formatVerify     ) where +import Data.Bifunctor (bimap, first) import Data.Text (Text)-import Text.Megaparsec (parse)+import qualified Text.Megaparsec as Megaparsec (parse) import Text.Megaparsec.Error (errorBundlePretty)  import Nixfmt.Parser (file)@@ -19,8 +21,27 @@ import Nixfmt.Pretty () import Nixfmt.Types (ParseErrorBundle) +type Width = Int+ -- | @format w filename source@ returns either a parsing error specifying a -- failure in @filename@ or a formatted version of @source@ with a maximum width -- of @w@ columns where possible.-format :: Int -> FilePath -> Text -> Either ParseErrorBundle Text-format width filename = fmap (layout width) . parse file filename+format :: Width -> FilePath -> Text -> Either String Text+format width filename+    = bimap errorBundlePretty (layout width)+    . Megaparsec.parse file filename++formatVerify :: Width -> FilePath -> Text -> Either String Text+formatVerify width path unformatted = do+    unformattedParsed <- parse unformatted+    let formattedOnce = layout width unformattedParsed+    formattedOnceParsed <- parse formattedOnce+    let formattedTwice = layout width formattedOnceParsed+    if formattedOnceParsed /= unformattedParsed+    then pleaseReport "Parses differently after formatting."+    else if formattedOnce /= formattedTwice+    then pleaseReport "Nixfmt is not idempotent."+    else Right formattedOnce+    where+        parse = first errorBundlePretty . Megaparsec.parse file path+        pleaseReport x = Left $ path <> ": " <> x <> " This is a bug in nixfmt. Please report it at https://github.com/serokell/nixfmt"
src/Nixfmt/Parser.hs view
@@ -16,23 +16,22 @@   (Operator(..), makeExprParser) import Data.Char (isAlpha) import Data.Foldable (toList)-import Data.Maybe (fromMaybe)-import Data.Text as Text-  (Text, cons, empty, null, singleton, split, strip, stripPrefix)+import Data.Maybe (fromMaybe, mapMaybe, maybeToList)+import Data.Text as Text (Text, cons, empty, singleton, split, stripPrefix) import Text.Megaparsec   (anySingle, chunk, eof, label, lookAhead, many, notFollowedBy, oneOf,-  optional, satisfy, try, (<|>))+  optional, satisfy, some, try, (<|>)) import Text.Megaparsec.Char (char) import qualified Text.Megaparsec.Char.Lexer as L (decimal, float)  import Nixfmt.Lexer (lexeme) import Nixfmt.Types   (Ann, Binder(..), Expression(..), File(..), Fixity(..), Leaf, Operator(..),-  ParamAttr(..), Parameter(..), Parser, Selector(..), SimpleSelector(..),+  ParamAttr(..), Parameter(..), Parser, Path, Selector(..), SimpleSelector(..),   String, StringPart(..), Term(..), Token(..), operators, tokenText) import Nixfmt.Util-  (commonIndentation, identChar, manyP, manyText, pathChar, schemeChar, someP,-  someText, uriChar)+  (commonIndentation, identChar, isSpaces, manyP, manyText, pathChar,+  schemeChar, someP, someText, uriChar)  -- HELPER FUNCTIONS @@ -83,9 +82,16 @@     someP pathChar <> manyText (slash <> someP pathChar)     <* char '>' -path :: Parser (Ann Token)-path = ann Path $ manyP pathChar <> someText (slash <> someP pathChar)+pathText :: Parser StringPart+pathText = TextPart <$> someP pathChar +pathTraversal :: Parser [StringPart]+pathTraversal = liftM2 (:) (TextPart <$> slash) (some (pathText <|> interpolation))++path :: Parser Path+path = try $ lexeme $ fmap normalizeLine $+    (maybeToList <$> optional pathText) <> (concat <$> some pathTraversal)+ uri :: Parser [[StringPart]] uri = fmap (pure . pure . TextPart) $ try $     someP schemeChar <> chunk ":" <> someP uriChar@@ -124,23 +130,28 @@  isEmptyLine :: [StringPart] -> Bool isEmptyLine []           = True-isEmptyLine [TextPart t] = Text.null (Text.strip t)+isEmptyLine [TextPart t] = isSpaces t isEmptyLine _            = False --- | Strip the first line of a string if it is empty.-stripFirstLine :: [[StringPart]] -> [[StringPart]]-stripFirstLine [] = []-stripFirstLine (x : xs)-    | isEmptyLine x = xs-    | otherwise     = x : xs+-- | Drop the first line of a string if it is empty.+fixFirstLine :: [[StringPart]] -> [[StringPart]]+fixFirstLine []       = []+fixFirstLine (x : xs) = if isEmptyLine x' then xs else x' : xs+    where x' = normalizeLine x -textHeads :: [StringPart] -> [Text]-textHeads line@(TextPart t : _)-    | isEmptyLine line              = []-    | otherwise                     = [t]-textHeads (Interpolation _ _ _ : _) = [""]-textHeads []                        = []+-- | Empty the last line if it contains only spaces.+fixLastLine :: [[StringPart]] -> [[StringPart]]+fixLastLine []       = []+fixLastLine [line]   = if isEmptyLine line' then [[]] else [line']+    where line' = normalizeLine line+fixLastLine (x : xs) = x : fixLastLine xs +lineHead :: [StringPart] -> Maybe Text+lineHead []                        = Nothing+lineHead line | isEmptyLine line   = Nothing+lineHead (TextPart t : _)          = Just t+lineHead (Interpolation _ _ _ : _) = Just ""+ stripParts :: Text -> [StringPart] -> [StringPart] stripParts indentation (TextPart t : xs) =     TextPart (fromMaybe Text.empty $ Text.stripPrefix indentation t) : xs@@ -162,28 +173,31 @@         _           -> error "unreachable"  stripIndentation :: [[StringPart]] -> [[StringPart]]-stripIndentation parts = case commonIndentation (concatMap textHeads parts) of+stripIndentation parts = case commonIndentation $ mapMaybe lineHead parts of     Nothing -> map (const []) parts     Just indentation -> map (stripParts indentation) parts -dropEmptyParts :: [[StringPart]] -> [[StringPart]]-dropEmptyParts = map $ filter (\case-    TextPart t | Text.null t -> False-    _                        -> True)+normalizeLine :: [StringPart] -> [StringPart]+normalizeLine [] = []+normalizeLine (TextPart "" : xs) = normalizeLine xs+normalizeLine (TextPart x : TextPart y : xs) = normalizeLine (TextPart (x <> y) : xs)+normalizeLine (x : xs) = x : normalizeLine xs  fixSimpleString :: [StringPart] -> [[StringPart]]-fixSimpleString parts = case splitLines parts of-    [] -> []-    [line] -> [line]-    parts' -> dropEmptyParts (stripIndentation parts')+fixSimpleString = map normalizeLine . splitLines  simpleString :: Parser [[StringPart]] simpleString = rawSymbol TDoubleQuote *>-    fmap splitLines (many (simpleStringPart <|> interpolation)) <*+    fmap fixSimpleString (many (simpleStringPart <|> interpolation)) <*     rawSymbol TDoubleQuote  fixIndentedString :: [[StringPart]] -> [[StringPart]]-fixIndentedString = dropEmptyParts . concatMap splitLines . stripIndentation . stripFirstLine+fixIndentedString+    = map normalizeLine+    . concatMap splitLines+    . stripIndentation+    . fixLastLine+    . fixFirstLine  indentedString :: Parser [[StringPart]] indentedString = rawSymbol TDoubleSingleQuote *>@@ -212,8 +226,8 @@     many (selector $ Just $ symbol TDot)  simpleTerm :: Parser Term-simpleTerm = (String <$> string) <|>-    (Token <$> (path <|> envPath <|> float <|> integer <|> identifier)) <|>+simpleTerm = (String <$> string) <|> (Path <$> path) <|>+    (Token <$> (envPath <|> float <|> integer <|> identifier)) <|>     parens <|> set <|> list  term :: Parser Term
src/Nixfmt/Pretty.hs view
@@ -23,7 +23,7 @@   (Ann(..), Binder(..), Expression(..), File(..), Leaf, ParamAttr(..),   Parameter(..), Selector(..), SimpleSelector(..), StringPart(..), Term(..),   Token(..), TrailingComment(..), Trivia, Trivium(..), tokenText)-import Nixfmt.Util (commonIndentation)+import Nixfmt.Util (commonIndentation, isSpaces, replaceMultiple)  prettyCommentLine :: Text -> Doc prettyCommentLine l@@ -88,6 +88,7 @@ prettyTerm :: Term -> Doc prettyTerm (Token t) = pretty t prettyTerm (String s) = pretty s+prettyTerm (Path p) = pretty p prettyTerm (Selection term selectors) = pretty term <> hcat selectors  prettyTerm (List (Ann paropen Nothing []) [] parclose)@@ -301,23 +302,32 @@           nonEmpty _                  = True           inits = map textInit $ filter nonEmpty parts -isEmptyLine :: [StringPart] -> Bool-isEmptyLine []           = True-isEmptyLine [TextPart t] = Text.null $ Text.strip t-isEmptyLine _            = False+-- | If the last line has at least one space but nothing else, it cannot be+-- cleanly represented in an indented string.+lastLineIsSpaces :: [[StringPart]] -> Bool+lastLineIsSpaces [] = False+lastLineIsSpaces xs = case last xs of+    [TextPart t] -> isSpaces t+    _            -> False +isInvisibleLine :: [StringPart] -> Bool+isInvisibleLine []           = True+isInvisibleLine [TextPart t] = Text.null $ Text.strip t+isInvisibleLine _            = False+ isSimpleString :: [[StringPart]] -> Bool isSimpleString [parts]-    | hasDualQuotes parts     = True-    | endsInSingleQuote parts = True-    | isIndented [parts]      = True-    | hasQuotes parts         = False-    | otherwise               = True+    | hasDualQuotes parts       = True+    | endsInSingleQuote parts   = True+    | isIndented [parts]        = True+    | hasQuotes parts           = False+    | otherwise                 = True  isSimpleString parts-    | all isEmptyLine parts = True-    | isIndented parts      = True-    | otherwise             = False+    | all isInvisibleLine parts = True+    | isIndented parts          = True+    | lastLineIsSpaces parts    = True+    | otherwise                 = False  instance Pretty StringPart where     pretty (TextPart t) = text t@@ -385,10 +395,11 @@     text "''" <> line'     <> nest 2 (sepBy newline (map (prettyLine escape unescapeInterpol) parts))     <> text "''"-    where escape-              = Text.replace "$''${" "$${"-              . Text.replace "${" "''${"-              . Text.replace "''" "'''"+    where escape = replaceMultiple+              [ ("'${", "''\\'''${")+              , ("${", "''${")+              , ("''", "'''")+              ]            unescapeInterpol t               | Text.null t        = t
src/Nixfmt/Types.hs view
@@ -24,59 +24,67 @@     = EmptyLine     | LineComment     Text     | BlockComment    [Text]-    deriving (Show)+    deriving (Eq, Show)  type Trivia = [Trivium] -newtype TrailingComment = TrailingComment Text deriving (Show)+newtype TrailingComment = TrailingComment Text deriving (Eq, Show)  data Ann a     = Ann a (Maybe TrailingComment) Trivia     deriving (Show) +-- | Equality of annotated syntax is defines as equality of their corresponding+-- semantics, thus ignoring the annotations.+instance Eq a => Eq (Ann a) where+    Ann x _ _ == Ann y _ _ = x == y+ type Leaf = Ann Token  data StringPart     = TextPart Text     | Interpolation Leaf Expression Token-    deriving (Show)+    deriving (Eq, Show) +type Path = Ann [StringPart]+ type String = Ann [[StringPart]]  data SimpleSelector     = IDSelector Leaf     | InterpolSelector (Ann StringPart)     | StringSelector String-    deriving (Show)+    deriving (Eq, Show)  data Selector     = Selector (Maybe Leaf) SimpleSelector (Maybe (Leaf, Term))-    deriving (Show)+    deriving (Eq, Show)  data Binder     = Inherit Leaf (Maybe Term) [Leaf] Leaf     | Assignment [Selector] Leaf Expression Leaf-    deriving (Show)+    deriving (Eq, Show)  data Term     = Token Leaf     | String String+    | Path Path     | List Leaf [Term] Leaf     | Set (Maybe Leaf) Leaf [Binder] Leaf     | Selection Term [Selector]     | Parenthesized Leaf Expression Leaf-    deriving (Show)+    deriving (Eq, Show)  data ParamAttr     = ParamAttr Leaf (Maybe (Leaf, Expression)) (Maybe Leaf)     | ParamEllipsis Leaf-    deriving (Show)+    deriving (Eq, Show)  data Parameter     = IDParameter Leaf     | SetParameter Leaf [ParamAttr] Leaf     | ContextParameter Parameter Leaf Parameter-    deriving (Show)+    deriving (Eq, Show)  data Expression     = Term Term@@ -91,17 +99,16 @@     | MemberCheck Expression Leaf [Selector]     | Negation Leaf Expression     | Inversion Leaf Expression-    deriving (Show)+    deriving (Eq, Show)  data File     = File Leaf Expression-    deriving (Show)+    deriving (Eq, Show)  data Token     = Integer    Int     | Float      Double     | Identifier Text-    | Path       Text     | EnvPath    Text      | KAssert@@ -165,12 +172,12 @@     | InfixN     | InfixR     | Postfix-    deriving (Show)+    deriving (Eq, Show)  data Operator     = Op Fixity Token     | Apply-    deriving (Show)+    deriving (Eq, Show)  -- | A list of lists of operators where lists that come first contain operators -- that bind more strongly.@@ -201,7 +208,6 @@ tokenText (Identifier i)     = i tokenText (Integer i)        = pack (show i) tokenText (Float f)          = pack (show f)-tokenText (Path p)           = p tokenText (EnvPath p)        = "<" <> p <> ">"  tokenText KAssert            = "assert"
src/Nixfmt/Util.hs view
@@ -4,6 +4,8 @@  - SPDX-License-Identifier: MPL-2.0  -} +{-# LANGUAGE TupleSections #-}+ module Nixfmt.Util     ( manyP     , someP@@ -13,15 +15,20 @@     , commonIndentation     , dropCommonIndentation     , identChar+    , isSpaces     , pathChar+    , replaceMultiple     , schemeChar     , uriChar     ) where +import Control.Applicative ((<|>)) import Data.Char (isAlpha, isDigit, isSpace)+import Data.Foldable (asum)+import Data.List (unfoldr) import Data.Maybe (fromMaybe) import Data.Text as Text-  (Text, commonPrefixes, concat, empty, stripEnd, stripPrefix, takeWhile)+  (Text, all, commonPrefixes, concat, empty, null, splitAt, stripEnd, stripPrefix, takeWhile) import Text.Megaparsec   (ParsecT, Stream, Token, Tokens, many, some, takeWhile1P, takeWhileP) @@ -78,3 +85,23 @@     in case commonIndentation (filter (/=empty) strippedLines) of             Nothing          -> map (const empty) strippedLines             Just indentation -> map (fromMaybe empty . stripPrefix indentation) strippedLines++isSpaces :: Text -> Bool+isSpaces = Text.all (==' ')++-- | Apply multiple independent replacements. This function passes over the text+-- once and applies the first replacement it can find at each position. After a+-- replacement is matched, the function continues after the replacement, not+-- inside it.+replaceMultiple :: [(Text, Text)] -> Text -> Text+replaceMultiple replacements = mconcat . unfoldr replaceAny+  where+    -- | replaceAny assumes input is nonempty+    replaceAny :: Text -> Maybe (Text, Text)+    replaceAny t+      | Text.null t = Nothing+      | otherwise   = asum (map (replaceStart t) replacements)+                      <|> Just (Text.splitAt 1 t)++    replaceStart :: Text -> (Text, Text) -> Maybe (Text, Text)+    replaceStart t (pat, rep) = (rep,) <$> Text.stripPrefix pat t