@@ -38,6 +38,7 @@ import qualified Data.Aeson as A
3838import Data.Align ( alignWith )
3939import Data.Array
4040import Data.Bits
41+ import Data.Bool ( bool )
4142import Data.ByteString ( ByteString )
4243import qualified Data.ByteString as B
4344import Data.ByteString.Base16 as Base16
@@ -923,56 +924,96 @@ genericClosure = fromValue @(AttrSet (NValue t f m)) >=> \s ->
923924 WValue j : _ -> checkComparable k' j
924925 fmap (t : ) <$> go op (ts <> ys) (S. insert (WValue k') ks)
925926
927+ -- | Takes:
928+ -- 1. List of strings to match.
929+ -- 2. List of strings to replace corresponding match occurance. (arg 1 & 2 lists matched by index)
930+ -- 3. String to process
931+ -- -> returns the string with requested replacements.
932+ --
933+ -- Example:
934+ -- builtins.replaceStrings ["ll" "e"] [" " "i"] "Hello world" == "Hi o world".
926935replaceStrings
927936 :: MonadNix e t f m
928937 => NValue t f m
929938 -> NValue t f m
930939 -> NValue t f m
931940 -> m (NValue t f m )
932- replaceStrings tfrom tto ts = fromValue (Deeper tfrom) >>= \ (nsFrom :: [NixString ]) ->
933- fromValue (Deeper tto) >>= \ (nsTo :: [NixString ]) ->
934- fromValue ts >>= \ (ns :: NixString ) -> do
935- let from = fmap stringIgnoreContext nsFrom
936- when (length nsFrom /= length nsTo)
937- $ throwError
938- $ ErrorCall
939- $ " 'from' and 'to' arguments to 'replaceStrings'"
940- <> " have different lengths"
941- let
942- lookupPrefix s = do
943- (prefix, replacement) <- find ((`Text.isPrefixOf` s) . fst )
944- $ zip from nsTo
945- let rest = Text. drop (Text. length prefix) s
946- pure (prefix, replacement, rest)
947- finish b =
948- makeNixString (LazyText. toStrict $ Builder. toLazyText b)
949- go orig result ctx = case lookupPrefix orig of
950- Nothing -> case Text. uncons orig of
951- Nothing -> finish result ctx
952- Just (h, t) -> go t (result <> Builder. singleton h) ctx
953- Just (prefix, replacementNS, rest) ->
954- let replacement = stringIgnoreContext replacementNS
955- newCtx = NixString. getContext replacementNS
956- in case prefix of
957- " " -> case Text. uncons rest of
958- Nothing -> finish
959- (result <> Builder. fromText replacement)
960- (ctx <> newCtx)
961- Just (h, t) -> go
962- t
963- (mconcat
964- [ result
965- , Builder. fromText replacement
966- , Builder. singleton h
967- ]
968- )
969- (ctx <> newCtx)
970- _ -> go rest
971- (result <> Builder. fromText replacement)
972- (ctx <> newCtx)
973- toValue
974- $ go (stringIgnoreContext ns) mempty
975- $ NixString. getContext ns
941+ replaceStrings tfrom tto ts =
942+ do
943+ -- NixStrings have context - remember
944+ (fromKeys :: [NixString ]) <- fromValue (Deeper tfrom)
945+ (toVals :: [NixString ]) <- fromValue (Deeper tto)
946+ (string :: NixString ) <- fromValue ts
947+
948+ when (length fromKeys /= length toVals) $ throwError $ ErrorCall " builtins.replaceStrings: Arguments `from`&`to` construct a key-value map, so the number of their elements must always match."
949+
950+ let
951+ -- 2021-02-18: NOTE: if there is no match - the process does not changes the context, simply slides along the string.
952+ -- So it isbe more effective to pass the context as the first argument.
953+ -- And moreover, the `passOneCharNgo` passively passes the context, to context can be removed from it and inherited directly.
954+ -- Then the solution would've been elegant, but the Nix bug prevents elegant implementation.
955+ go ctx input output =
956+ maybe
957+ -- Passively pass the chars
958+ passOneChar
959+ replace
960+ maybePrefixMatch
961+
962+ where
963+ -- When prefix matched something - returns (match, replacement, reminder)
964+ maybePrefixMatch :: Maybe (Text , NixString , Text )
965+ maybePrefixMatch = formMatchReplaceTailInfo <$> find ((`Text.isPrefixOf` input) . fst ) fromKeysToValsMap
966+ where
967+ formMatchReplaceTailInfo = (\ (m, r) -> (m, r, Text. drop (Text. length m) input))
968+
969+ fromKeysToValsMap = zip (fmap stringIgnoreContext fromKeys) toVals
970+
971+ -- Not passing args => It is constant that gets embedded into `go` => It is simple `go` tail recursion
972+ passOneChar =
973+ maybe
974+ (finish ctx output) -- The base case - there is no chars left to process -> finish
975+ (\ (c, i) -> go ctx i (output <> Builder. singleton c)) -- If there are chars - pass one char & continue
976+ (Text. uncons input) -- chip first char
977+
978+ -- 2021-02-18: NOTE: rly?: toStrict . toLazyText
979+ -- Maybe `text-builder`, `text-show`?
980+ finish ctx output = makeNixString (LazyText. toStrict $ Builder. toLazyText output) ctx
981+
982+ replace (key, replacementNS, unprocessedInput) = replaceWithNixBug unprocessedInput updatedOutput
983+
984+ where
985+ replaceWithNixBug =
986+ bool
987+ (go updatedCtx) -- tail recursion
988+ -- Allowing match on "" is a inherited bug of Nix,
989+ -- when "" is checked - it always matches. And so - when it checks - it always insers a replacement, and then process simply passesthrough the char that was under match.
990+ --
991+ -- repl> builtins.replaceStrings ["" "e"] [" " "i"] "Hello world"
992+ -- " H e l l o w o r l d "
993+ -- repl> builtins.replaceStrings ["ll" ""] [" " "i"] "Hello world"
994+ -- "iHie ioi iwioirilidi"
995+ -- 2021-02-18: NOTE: There is no tests for this
996+ bugPassOneChar -- augmented recursion
997+ isNixBugCase
998+
999+ isNixBugCase = key == mempty
1000+
1001+ updatedOutput = output <> replacement
1002+ updatedCtx = ctx <> replacementCtx
1003+
1004+ replacement = Builder. fromText $ stringIgnoreContext replacementNS
1005+ replacementCtx = NixString. getContext replacementNS
1006+
1007+ -- The bug modifies the content => bug demands `pass` to be a real function =>
1008+ -- `go` calls `pass` function && `pass` calls `go` function
1009+ -- => mutual recusion case, so placed separately.
1010+ bugPassOneChar input output =
1011+ maybe
1012+ (finish updatedCtx output) -- The base case - there is no chars left to process -> finish
1013+ (\ (c, i) -> go updatedCtx i (output <> Builder. singleton c)) -- If there are chars - pass one char & continue
1014+ (Text. uncons input) -- chip first char
1015+
1016+ toValue $ go (NixString. getContext string) (stringIgnoreContext string) mempty
9761017
9771018removeAttrs
9781019 :: forall e t f m
0 commit comments