From 0608ae982063ecf9b29443da0bc86c44a04cf0ea Mon Sep 17 00:00:00 2001 From: Leonid Vasilev Date: Tue, 5 Apr 2022 22:37:02 +0300 Subject: [PATCH] [#39] String interpolation in `coffer` output messages Problem: at this moment constructing some messages in `Main.hs` looks very convoluted (e.g. message in `set-field` command). Solution: used `nyan-interpolation` package in `Main.hs`. --- .hlint.yaml | 2 +- app/Main.hs | 125 +++++++++++++++++++++++----------------- cabal.project.freeze | 2 + coffer.cabal | 2 + lib/Backend/Vault/Kv.hs | 61 ++++++++++---------- lib/CLI/Parser.hs | 90 +++++++++++++++-------------- lib/Error.hs | 49 ++++++++-------- nix/sources.json | 6 +- package.yaml | 2 + stack.yaml | 2 + stack.yaml.lock | 14 +++++ 11 files changed, 200 insertions(+), 155 deletions(-) diff --git a/.hlint.yaml b/.hlint.yaml index 7ecaa7f9..180cc78a 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -6,7 +6,7 @@ # Settings ########################################################################### -- arguments: [-XTypeApplications, -XRecursiveDo, -XBlockArguments] +- arguments: [-XTypeApplications, -XRecursiveDo, -XBlockArguments, -XQuasiQuotes] # These are just too annoying - ignore: { name: Redundant do } diff --git a/app/Main.hs b/app/Main.hs index 735fa37e..c164ee89 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -14,7 +14,7 @@ import Coffer.Directory qualified as Dir import Coffer.Path (EntryPath, Path, QualifiedPath(qpPath)) import Config (Config(..), configCodec) import Control.Lens -import Control.Monad (forM, forM_, when) +import Control.Monad (forM_, when) import Data.Maybe (fromMaybe) import Data.Text (pack) import Data.Text.IO qualified as TIO @@ -26,6 +26,7 @@ import Polysemy import Polysemy.Error (Error, errorToIOFinal) import System.Environment (lookupEnv) import System.Exit (die, exitFailure) +import Text.Interpolation.Nyan import Toml qualified runBackendIO @@ -74,43 +75,52 @@ main = do VREntry entry -> pprint $ buildDirectory $ Dir.singleton entry VRField _ field -> pprint $ build $ field ^. E.contents VRPathNotFound path -> pathNotFound path - VRDirectoryNoFieldMatch path fieldName -> printError $ - "There are no entries at path '" +| path |+ "' with the field '" +| fieldName |+ "'." - VREntryNoFieldMatch path fieldName -> printError $ - "The entry at '" +| path |+ "' does not have a field '" +| fieldName |+ "'." + VRDirectoryNoFieldMatch path fieldName -> printError [int|s| + There are no entries at path '#{path}' with the field '#{fieldName}'. + |] + + VREntryNoFieldMatch path fieldName -> printError [int|s| + The entry at '#{path}' does not have a field '#{fieldName}'. + |] SomeCommand cmd@(CmdCreate opts) -> do runCommand config cmd >>= \case - CRSuccess _ -> printSuccess $ "Entry created at '" +| coQPath opts |+ "'." + CRSuccess _ -> printSuccess [int|s|Entry created at '#{coQPath opts}'.|] CRCreateError error -> do let errorMsg = createErrorToBuilder error - printError $ unlinesF @_ @Builder $ "The entry cannot be created:" : "" : [errorMsg] + printError [int|s| + The entry cannot be created: + + #{errorMsg} + |] SomeCommand cmd@(CmdSetField opts) -> do let fieldName = sfoFieldName opts runCommand config cmd >>= \case SFREntryNotFound path -> entryNotFound path - SFRMissingFieldContents path -> printError $ unlinesF @_ @Builder - [ "The entry at '" +| path |+ "' does not yet have a field '" +| fieldName |+ "'." - , "In order to create a new field, please include the 'FIELDCONTENTS' argument." - ] + SFRMissingFieldContents path -> printError [int|s| + The entry at '#{path}' does not yet have a field '#{fieldName}'. + In order to create a new field, please include the 'FIELDCONTENTS' argument. + |] SFRSuccess qEntry -> do let entry = qpPath qEntry let qPath = view E.path <$> qEntry let field = entry ^?! E.fields . ix fieldName - printSuccess $ - "Set field '" +| fieldName |+ - "' (" +| (field ^. E.visibility) |+ - ") at '" +| qPath |+ - "' to:\n" +| (field ^. E.contents) |+ "" + printSuccess [int|s| + Set field '#{fieldName}' (#{field ^. E.visibility}) \ + at '#{qPath}' to: + #{field ^. E.contents} + |] SomeCommand cmd@(CmdDeleteField opts) -> do runCommand config cmd >>= \case DFREntryNotFound path -> entryNotFound path - DFRFieldNotFound fieldName -> printError $ - "Entry does not have a field with name '" +| fieldName |+ "'." - DFRSuccess _ -> printSuccess $ - "Deleted field '" +| dfoFieldName opts |+ "' from '" +| dfoQPath opts |+ "'." + DFRFieldNotFound fieldName -> printError [int|s| + Entry does not have a field with name '#{fieldName}'. + |] + DFRSuccess _ -> printSuccess [int|s| + Deleted field '#{dfoFieldName opts}' from '#{dfoQPath opts}'. + |] SomeCommand cmd@CmdFind{} -> do runCommand config cmd >>= \case @@ -123,13 +133,17 @@ main = do when (roDryRun opts) do pprint "These actions would be done:" forM_ copiedPaths \(from, to) -> - printSuccess $ "Renamed '" +| from |+ "' to '" +| to |+ "'." + printSuccess [int|s|Renamed '#{from}' to '#{to}'.|] CPRPathNotFound path -> pathNotFound path CPRMissingEntryName -> printError "The destination path is not a valid entry path. Please specify the new name of the entry." CPRCreateErrors errors -> do - errorMsgs <- buildErrorMessages errors - printError $ unlinesF @_ @Builder $ "The following entries cannot be renamed:" : "" : errorMsgs + let errorMsgs = buildErrorMessages errors + printError [int|s| + The following entries cannot be renamed: + + #{unlinesF errorMsgs} + |] CPRSamePath path -> samePaths path SomeCommand cmd@(CmdCopy opts) -> do @@ -138,39 +152,43 @@ main = do when (cpoDryRun opts) do pprint "These actions would be done:" forM_ copiedPaths \(from, to) -> - printSuccess $ "Copied '" +| from |+ "' to '" +| to |+ "'." + printSuccess [int|s|Copied '#{from}' to '#{to}'.|] CPRPathNotFound path -> pathNotFound path CPRMissingEntryName -> printError "The destination path is not a valid entry path. Please specify the new name of the entry." CPRCreateErrors errors -> do - errorMsgs <- buildErrorMessages errors - printError $ unlinesF @_ @Builder $ "The following entries cannot be copied:" : "" : errorMsgs + let errorMsgs = buildErrorMessages errors + printError [int|s| + The following entries cannot be copied: + + #{unlinesF errorMsgs} + |] CPRSamePath path -> samePaths path SomeCommand cmd@(CmdDelete opts) -> do runCommand config cmd >>= \case DRPathNotFound path -> pathNotFound path - DRDirectoryFound path -> printError $ unlinesF @_ @Builder - [ "The path '" +| path |+ "' is a directory." - , "Use '--recursive' or '-r' to recursively delete all entries." - ] + DRDirectoryFound path -> printError [int|s| + The path '#{path}' is a directory. + Use '--recursive' or '-r' to recursively delete all entries. + |] DRSuccess paths -> do when (doDryRun opts) do pprint "These actions would be done:" forM_ paths \path -> - printSuccess $ "Deleted '" +| path |+ "'." + printSuccess [int|s|Deleted '#{path}'.|] SomeCommand cmd@(CmdTag opts) -> do runCommand config cmd >>= \case TREntryNotFound path -> entryNotFound path TRSuccess _ -> if toDelete opts - then printSuccess $ "Removed tag '" +| toTagName opts |+ "' from '" +| toQPath opts |+ "'." - else printSuccess $ "Added tag '" +| toTagName opts |+ "' to '" +| toQPath opts |+ "'." - TRTagNotFound tag -> printError $ - "Entry does not have the tag '" +| tag |+ "'." - TRDuplicateTag tag -> printError $ - "Entry already has the tag '" +| tag |+ "'." + then printSuccess [int|s|Removed tag '#{toTagName opts}' from '#{toQPath opts}'.|] + else printSuccess [int|s|Added tag '#{toTagName opts}' to '#{toQPath opts}'.|] + TRTagNotFound tag -> printError + [int|s|Entry does not have the tag '#{tag}'.|] + TRDuplicateTag tag -> printError + [int|s|Entry already has the tag '#{tag}'.|] where -- | Pretty-print a message. pprint :: Member (Embed IO) r => Builder -> Sem r () @@ -185,24 +203,24 @@ main = do printError msg = embed $ die $ "[ERROR] " <> fmt msg entryNotFound :: Member (Embed IO) r => QualifiedPath EntryPath -> Sem r () - entryNotFound path = printError $ "Entry not found at '" +| path |+ "'." + entryNotFound path = printError [int|s|Entry not found at '#{path}'.|] pathNotFound :: Member (Embed IO) r => QualifiedPath Path -> Sem r () - pathNotFound path = printError $ "Entry or directory not found at '" +| path |+ "'." + pathNotFound path = printError [int|s|Entry or directory not found at '#{path}'.|] samePaths :: Member (Embed IO) r => QualifiedPath Path -> Sem r () samePaths path = - printError $ "'" +| path |+ "' and '" +| path |+ "' are the same path." + printError [int|s|'#{path}' and '#{path}' are the same path.|] createErrorToBuilder :: CreateError -> Builder createErrorToBuilder = \case - CEEntryAlreadyExists entryPath -> unlinesF @_ @Builder - [ "An entry already exists at '" +| entryPath |+ "'." - , "Use '--force' or '-f' to overwrite existing entries." - ] - CEDestinationIsDirectory entryPath -> "'" +| entryPath |+ "' is a directory." + CEEntryAlreadyExists entryPath -> [int|s| + An entry already exists at '#{entryPath}'. + Use '--force' or '-f' to overwrite existing entries. + |] + CEDestinationIsDirectory entryPath -> [int|s|'#{entryPath}' is a directory.|] CEParentDirectoryIsEntry (_, clashed) -> - "Attempted to create the directory '" +| clashed |+ "' but an entry exists at that path." + [int|s|Attempted to create the directory '#{clashed}' but an entry exists at that path.|] getEntryFromCreateError :: CreateError -> QualifiedPath EntryPath getEntryFromCreateError = \case @@ -210,10 +228,11 @@ main = do CEDestinationIsDirectory entryPath -> entryPath CEEntryAlreadyExists entryPath -> entryPath - buildErrorMessages :: [(QualifiedPath EntryPath, CreateError)] -> Sem r [Builder] - buildErrorMessages errors = do - forM errors \(from, err) -> do - let entryPath = getEntryFromCreateError err - let header = "'" +| from |+ "' to '" +| entryPath |+ "':" - let errorMsg = createErrorToBuilder err - pure $ unlinesF @_ @Builder $ header : [indentF 2 errorMsg] + buildErrorMessages :: [(QualifiedPath EntryPath, CreateError)] -> [Builder] + buildErrorMessages = + fmap \(from, err) -> + let + entryPath = getEntryFromCreateError err + header = [int|s|'#{from}' to '#{entryPath}':|] + errorMsg = createErrorToBuilder err + in unlinesF @_ @Builder $ header : [indentF 2 errorMsg] diff --git a/cabal.project.freeze b/cabal.project.freeze index 6f79c076..55deffa8 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -1713,6 +1713,8 @@ constraints: any.AC-Angle ==1.0, any.nvim-hs ==2.1.0.4, any.nvim-hs-contrib ==2.0.0.0, any.nvim-hs-ghcid ==2.0.0.0, + any.nyan-interpolation ==0.9, + any.nyan-interpolation-core ==0.9.0.1, any.o-clock ==1.2.1, any.oauthenticated ==0.2.1.0, any.odbc ==0.2.5, diff --git a/coffer.cabal b/coffer.cabal index cb9cbf2d..172125a5 100644 --- a/coffer.cabal +++ b/coffer.cabal @@ -106,6 +106,7 @@ library , lens-aeson , megaparsec , mtl + , nyan-interpolation , optparse-applicative , polysemy , servant @@ -181,6 +182,7 @@ executable coffer , coffer , fmt , lens + , nyan-interpolation , optparse-applicative , polysemy , text diff --git a/lib/Backend/Vault/Kv.hs b/lib/Backend/Vault/Kv.hs index 5a67c618..e7f07be9 100644 --- a/lib/Backend/Vault/Kv.hs +++ b/lib/Backend/Vault/Kv.hs @@ -31,7 +31,7 @@ import Data.Time (UTCTime) import Entry (Entry, Field, FieldContents(FieldContents), FieldName, FieldVisibility) import Entry qualified as E import Error (BackendError, CofferError(..)) -import Fmt (Buildable(build), Builder, indentF, unlinesF, (+|), (|+)) +import Fmt (Buildable(build)) import GHC.Generics (Generic) import Network.HTTP.Client (defaultManagerSettings, newManager) import Network.HTTP.Client.TLS (tlsManagerSettings) @@ -42,6 +42,7 @@ import Servant.Client (BaseUrl(BaseUrl), ClientEnv, ClientError(..), Scheme(Http, Https), mkClientEnv, parseBaseUrl, showBaseUrl) import Servant.Client.Core.Response (responseStatusCode) +import Text.Interpolation.Nyan import Toml (TomlCodec, (.=)) import Toml qualified @@ -64,41 +65,41 @@ data VaultError instance Buildable VaultError where build = \case ServantError (FailureResponse request response) -> - unlinesF @_ @Builder - [ "Request:" - , indentF 2 ((build . show) request) - , "failed with response:" - , indentF 2 ((build . show) response) - ] + [int|s| + Request: + #{show request} + failed with response: + #{show response} + |] ServantError (DecodeFailure body response) -> - unlinesF @_ @Builder - [ "The body could not be decoded at the expected type." - , "Body: " <> build body - , "Response:" - , indentF 2 ((build . show) response) - ] + [int|s| + The body could not be decoded at the expected type. + Body: #{body} + Response: + #{show response} + |] ServantError (UnsupportedContentType mediatype response) -> - unlinesF @_ @Builder - [ "The content-type '" <> (build . show) mediatype <> "' of the response is not supported." - , "Response:" - , indentF 2 ((build . show) response) - ] + [int|s| + The content-type '#{show mediatype}' of the response is not supported. + Response: + #{show response} + |] ServantError (InvalidContentTypeHeader response) -> - unlinesF @_ @Builder - [ "The content-type header is invalid." - , "Response:" - , indentF 2 ((build . show) response) - ] + [int|s| + The content-type header is invalid. + Response: + #{show response} + |] ServantError (ConnectionError exception) -> - unlinesF @_ @Builder - [ "Connection error. No response was received." - , (build . show) exception - ] + [int|s| + Connection error. No response was received. + #{show exception} + |] FieldMetadataNotFound entryPath fieldName -> - "Could not find coffer metadata for field '" +| fieldName - |+ "' at '" +| entryPath |+ "'" + [int|s|Could not find coffer metadata for field \ + '#{fieldName}' at '#{entryPath}'|] CofferSpecialsNotFound entryPath -> - "Could not find key '#$coffer' in the kv entry at '" +| entryPath |+ "'." + [int|s|Could not find key '#$coffer' in the kv entry at '#{entryPath}'.|] BadCofferSpecialsError err -> build err instance BackendError VaultError diff --git a/lib/CLI/Parser.hs b/lib/CLI/Parser.hs index 481e307e..2759f72e 100644 --- a/lib/CLI/Parser.hs +++ b/lib/CLI/Parser.hs @@ -35,6 +35,7 @@ import Entry import Fmt (pretty) import Options.Applicative import Options.Applicative.Help.Pretty qualified as Pretty +import Text.Interpolation.Nyan import Text.Megaparsec (try) import Text.Megaparsec qualified as P import Text.Megaparsec.Char qualified as P @@ -60,11 +61,11 @@ parser = Options [ long "config" , short 'c' , metavar "CONFIG" - , help $ unlines - [ "Specify config file path." - , "When this option is not set, the 'COFFER_CONFIG' environment variable will be used." - , "When neither is set, it will default to 'config.toml'." - ] + , help [int|s| + Specify config file path. + When this option is not set, the 'COFFER_CONFIG' environment variable will be used. + When neither is set, it will default to 'config.toml'. + |] ] commandParser :: Parser SomeCommand @@ -401,11 +402,10 @@ readQualifiedPath = do [pathStr] -> do path <- readPath' pathStr pure $ QualifiedPath Nothing path - _ -> - Left $ unlines - [ "Invalid qualified path format: " <> show input <> "." - , show expectedQualifiedPathFormat - ] + _ -> Left [int|s| + Invalid qualified path format: #{show input}. + #{show expectedQualifiedPathFormat} + |] readFieldContents :: ReadM FieldContents readFieldContents = str <&> FieldContents @@ -413,13 +413,14 @@ readFieldContents = str <&> FieldContents readFieldInfo :: ReadM FieldInfo readFieldInfo = do eitherReader \input -> - P.parse (parseFieldInfo <* P.eof) "" (T.pack input) & first \err -> unlines - [ "Invalid field format: " <> show input <> "." - , "Expected format: 'fieldname=fieldcontents'." - , "" - , "Parser error:" - , P.errorBundlePretty err - ] + P.parse (parseFieldInfo <* P.eof) "" (T.pack input) & first \err -> + [int|s| + Invalid field format: #{show input}. + Expected format: 'fieldname=fieldcontents'. + + Parser error: + #{P.errorBundlePretty err} + |] readSort :: ReadM (Sort, Direction) readSort = do @@ -430,28 +431,28 @@ readSort = do case means of "name" -> pure (SortByEntryName, direction') "date" -> pure (SortByEntryDate, direction') - _ -> Left $ unlines - [ "Invalid sort: " <> show means <> "." - , "Choose one of: 'name', 'date'." - , "" - , show expectedSortFormat - ] + _ -> Left [int|s| + Invalid sort: #{show means}. + Choose one of: 'name', 'date'. + + #{show expectedSortFormat} + |] [fieldName, means, direction] -> do fieldName' <- readFieldName' fieldName direction' <- readDirection direction case means of "contents" -> pure (SortByFieldContents fieldName', direction') "date" -> pure (SortByFieldDate fieldName', direction') - _ -> Left $ unlines - [ "Invalid sort: " <> show means <> "." - , "Choose one of: 'contents', 'date'." - , "" - , show expectedSortFormat - ] - _ -> Left $ unlines - [ "Invalid sort format: " <> show input <> "." - , show expectedSortFormat - ] + _ -> Left [int|s| + Invalid sort: #{show means}. + Choose one of: 'contents', 'date'. + + #{show expectedSortFormat} + |] + _ -> Left [int|s| + Invalid sort format: #{show input}. + #{show expectedSortFormat} + |] expectedSortFormat :: Pretty.Doc expectedSortFormat = Pretty.vsep @@ -474,13 +475,14 @@ readDirection = readFilter :: ReadM Filter readFilter = do eitherReader \input -> - P.parse (parseFilter <* P.eof) "" (T.pack input) & first \err -> unlines - [ "Invalid filter format: " <> show input <> "." - , show expectedFilterFormat - , "" - , "Parser error:" - , P.errorBundlePretty err - ] + P.parse (parseFilter <* P.eof) "" (T.pack input) & first \err -> + [int|s| + Invalid filter format: #{show input}. + #{show expectedFilterFormat} + + Parser error: + #{P.errorBundlePretty err} + |] expectedQualifiedEntryPathFormat :: Pretty.Doc expectedQualifiedEntryPathFormat = Pretty.vsep @@ -624,10 +626,10 @@ readSum sumDescription constructors input = case M.lookup input constructors of Just cons -> Right cons Nothing -> - Left $ unlines - [ "Invalid " <> sumDescription <> ": '" <> input <> "'." - , "Choose one of: " <> constructorNames <> "." - ] + Left [int|s| + Invalid #{sumDescription}: '#{input}'. + Choose one of: #{constructorNames}. + |] where constructorNames :: String constructorNames = diff --git a/lib/Error.hs b/lib/Error.hs index fbaae18e..710d3a2b 100644 --- a/lib/Error.hs +++ b/lib/Error.hs @@ -12,7 +12,8 @@ import BackendName (BackendName) import Coffer.Path (EntryPath, Path) import Data.Text (Text) import Entry (BadEntryTag, BadFieldName) -import Fmt (Buildable(build), Builder, pretty, unlinesF, (+|), (|+)) +import Fmt (Buildable(build)) +import Text.Interpolation.Nyan -- | GADT for coffer internal errors. -- It is backend-agnostic, so it doesn't know about specific backend errors. @@ -35,36 +36,36 @@ data InternalCommandsError instance Buildable InternalCommandsError where build = \case InvalidEntry entry -> - unlinesF @_ @Builder - [ "Backend returned a secret that is not a valid\ - \ entry or directory name." - , "Got: '" +| entry |+ "'." - ] + [int|s| + Backend returned a secret that is not a valid \ + entry or directory name. + Got: '#{entry}'. + |] EntryPathDoesntHavePrefix entryPath path -> - unlinesF @_ @Builder - [ "Expected path: '" <> pretty entryPath <> "'" - , "To have the prefix: '" <> pretty path <> "'" - ] + [int|s| + Expected path: '#{entryPath}' + To have the prefix: '#{path}' + |] instance Buildable CofferError where build = \case BackendError err -> - unlinesF @_ @Builder - [ "Internal backend error:" - , build err - ] + [int|s| + Internal backend error: + #{err} + |] InternalCommandsError err -> - unlinesF @_ @Builder - [ "Internal error:" - , build err - ] + [int|s| + Internal error: + #{err} + |] BackendNotFound backendName -> - "Backend with name '" <> build backendName <> "' not found." + [int|s|Backend with name '#{backendName}' not found.|] BadFieldNameError err -> build err BadMasterFieldName name err -> - unlinesF @_ @Builder - [ "Attempted to create new field name from '" +| name |+ "'" - , "" - , build err - ] + [int|s| + Attempted to create new field name from '#{name}' + + #{err} + |] BadEntryTagError err -> build err diff --git a/nix/sources.json b/nix/sources.json index 95301c06..691dca36 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,10 +5,10 @@ "homepage": "", "owner": "input-output-hk", "repo": "hackage.nix", - "rev": "4cf90b36955597d0151940eabfb1b61a8ec42256", - "sha256": "1gdy89dgv2n5ibb6lc03y6k0y9pcacdrlfgv6ipd9bwrivkhdaa9", + "rev": "aee511167e363943d6d28e0d45244abca38cbd47", + "sha256": "11c4vrxcvxxz82kvjir4253fif4iv17439v3y4ijblnxvwi4znhw", "type": "tarball", - "url": "https://github.com/input-output-hk/hackage.nix/archive/4cf90b36955597d0151940eabfb1b61a8ec42256.tar.gz", + "url": "https://github.com/input-output-hk/hackage.nix/archive/aee511167e363943d6d28e0d45244abca38cbd47.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "haskell-nix-weeder": { diff --git a/package.yaml b/package.yaml index a27623ae..6bb320d6 100644 --- a/package.yaml +++ b/package.yaml @@ -101,6 +101,7 @@ library: - lens-aeson - megaparsec - mtl + - nyan-interpolation - optparse-applicative - polysemy - servant @@ -121,6 +122,7 @@ executables: - coffer - fmt - lens + - nyan-interpolation - optparse-applicative - polysemy - text diff --git a/stack.yaml b/stack.yaml index 734e7142..6c34eead 100644 --- a/stack.yaml +++ b/stack.yaml @@ -18,6 +18,8 @@ extra-deps: - generic-lens-core-2.2.0.0@sha256:b6b69e992f15fa80001de737f41f2123059011a1163d6c8941ce2e3ab44f8c03,2913 - hashable-1.3.5.0@sha256:47d1232d9788bb909cfbd80618de18dcdfb925609593e202912bd5841db138c1,4193 - lens-5.1@sha256:eb01fc4b1cfbad0e94c497eaf7b9f0e9b6c3dc7645c8b4597da7dc9d579f8500,14519 +- nyan-interpolation-0.9@sha256:8cf238be4c04746e4e9eabb34001c990c23e5837a19eb8652c584e57e92ecb41,3797 +- nyan-interpolation-core-0.9.0.1@sha256:1bda0e90d2045eb18c905f905082f4098829c1bdcbc4012663686a1c503b4ded,4067 - polysemy-1.7.1.0@sha256:3ead7a332abd70b202920ed3bf2e36866de163f821e643adfdcc9d39867b8033,5977 - time-compat-1.9.6.1@sha256:42d8f2e08e965e1718917d54ad69e1d06bd4b87d66c41dc7410f59313dba4ed1,5033 - tomland-1.3.3.1@sha256:83a8fd26a97164100541f7b26aa40ffdc6f230b21e94cbb3eae1fb7093c4356e,8924 diff --git a/stack.yaml.lock b/stack.yaml.lock index 4d72cfb2..de4d1fda 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -32,6 +32,20 @@ packages: sha256: 6236dbada87c86dfc74c3260acc674145b2773b354ac040c65abc54740452e07 original: hackage: lens-5.1@sha256:eb01fc4b1cfbad0e94c497eaf7b9f0e9b6c3dc7645c8b4597da7dc9d579f8500,14519 +- completed: + hackage: nyan-interpolation-0.9@sha256:8cf238be4c04746e4e9eabb34001c990c23e5837a19eb8652c584e57e92ecb41,3797 + pantry-tree: + size: 661 + sha256: 1ba3d0b9c1dd65cd6c8a3e10dc31261c70366d5822281176b84617b6c7b7bbc1 + original: + hackage: nyan-interpolation-0.9@sha256:8cf238be4c04746e4e9eabb34001c990c23e5837a19eb8652c584e57e92ecb41,3797 +- completed: + hackage: nyan-interpolation-core-0.9.0.1@sha256:1bda0e90d2045eb18c905f905082f4098829c1bdcbc4012663686a1c503b4ded,4067 + pantry-tree: + size: 1463 + sha256: 2a1a8d8b66746a246b3c0a4cd07daa6b961c9911676d30bc308a8a2682353b2b + original: + hackage: nyan-interpolation-core-0.9.0.1@sha256:1bda0e90d2045eb18c905f905082f4098829c1bdcbc4012663686a1c503b4ded,4067 - completed: hackage: polysemy-1.7.1.0@sha256:3ead7a332abd70b202920ed3bf2e36866de163f821e643adfdcc9d39867b8033,5977 pantry-tree: