From a41e8d0bd7f5ea953eb83aa7339bd9ca038b3532 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Mon, 13 May 2024 14:41:13 -0600 Subject: [PATCH 01/10] Use `NumberedArgs` type consistently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As the type is changing to be more structured, we can’t use `[String]` in its place. --- unison-cli/src/Unison/CommandLine.hs | 5 +++-- unison-cli/src/Unison/CommandLine/Main.hs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/unison-cli/src/Unison/CommandLine.hs b/unison-cli/src/Unison/CommandLine.hs index f97a25e3311..38d53a4a8bb 100644 --- a/unison-cli/src/Unison/CommandLine.hs +++ b/unison-cli/src/Unison/CommandLine.hs @@ -46,6 +46,7 @@ import Unison.Codebase qualified as Codebase import Unison.Codebase.Branch (Branch0) import Unison.Codebase.Branch qualified as Branch import Unison.Codebase.Editor.Input (Event (..), Input (..)) +import Unison.Codebase.Editor.Output (NumberedArgs) import Unison.Codebase.Path qualified as Path import Unison.Codebase.Watch qualified as Watch import Unison.CommandLine.FZFResolvers qualified as FZFResolvers @@ -123,7 +124,7 @@ parseInput :: -- | Current path from root Path.Absolute -> -- | Numbered arguments - [String] -> + NumberedArgs -> -- | Input Pattern Map Map String InputPattern -> -- | command:arguments @@ -168,7 +169,7 @@ parseInput codebase currentPath numberedArgs patterns segments = runExceptT do ] -- Expand a numeric argument like `1` or a range like `3-9` -expandNumber :: [String] -> String -> [String] +expandNumber :: NumberedArgs -> String -> [String] expandNumber numberedArgs s = case expandedNumber of Nothing -> [s] Just nums -> diff --git a/unison-cli/src/Unison/CommandLine/Main.hs b/unison-cli/src/Unison/CommandLine/Main.hs index de0d7e12fbb..0e948b5da02 100644 --- a/unison-cli/src/Unison/CommandLine/Main.hs +++ b/unison-cli/src/Unison/CommandLine/Main.hs @@ -33,7 +33,7 @@ import Unison.Codebase qualified as Codebase import Unison.Codebase.Branch qualified as Branch import Unison.Codebase.Editor.HandleInput qualified as HandleInput import Unison.Codebase.Editor.Input (Event, Input (..)) -import Unison.Codebase.Editor.Output (Output) +import Unison.Codebase.Editor.Output (NumberedArgs, Output) import Unison.Codebase.Editor.UCMVersion (UCMVersion) import Unison.Codebase.Path qualified as Path import Unison.Codebase.Runtime qualified as Runtime @@ -61,7 +61,7 @@ getUserInput :: Codebase IO Symbol Ann -> AuthenticatedHttpClient -> Path.Absolute -> - [String] -> + NumberedArgs -> IO Input getUserInput codebase authHTTPClient currentPath numberedArgs = Line.runInputT From e250d0598054f6744f48ea66c90c8ab397330a20 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 14 May 2024 00:27:26 -0600 Subject: [PATCH 02/10] Maintain `NumberedArgs` as structured data This is the first step toward avoiding printing/parsing the values provided via `NumberedArgs`. It simply adds a new sum type to hold all of the types that can be in numbered args and stores it alongside the `Text` representation. It currently gets discarded when we actually expand the arguments. --- unison-cli/src/Unison/Cli/Pretty.hs | 4 +- .../src/Unison/Codebase/Editor/HandleInput.hs | 64 +++++++++-------- .../Editor/HandleInput/FindAndReplace.hs | 4 +- .../src/Unison/Codebase/Editor/Output.hs | 8 ++- .../Codebase/Editor/StructuredArgument.hs | 31 ++++++++ unison-cli/src/Unison/CommandLine.hs | 2 +- .../src/Unison/CommandLine/OutputMessages.hs | 70 +++++++++++-------- unison-cli/tests/Unison/Test/Cli/Monad.hs | 6 +- unison-cli/unison-cli.cabal | 1 + 9 files changed, 124 insertions(+), 66 deletions(-) create mode 100644 unison-cli/src/Unison/Codebase/Editor/StructuredArgument.hs diff --git a/unison-cli/src/Unison/Cli/Pretty.hs b/unison-cli/src/Unison/Cli/Pretty.hs index 4ec00b02fe6..d3e1f2bfff3 100644 --- a/unison-cli/src/Unison/Cli/Pretty.hs +++ b/unison-cli/src/Unison/Cli/Pretty.hs @@ -345,8 +345,8 @@ prettyWhichBranchEmpty = \case WhichBranchEmptyPath path -> prettyPath' path -- | Displays a full, non-truncated Branch.CausalHash to a string, e.g. #abcdef -displayBranchHash :: CausalHash -> String -displayBranchHash = ("#" <>) . Text.unpack . Hash.toBase32HexText . unCausalHash +displayBranchHash :: CausalHash -> Text +displayBranchHash = ("#" <>) . Hash.toBase32HexText . unCausalHash prettyHumanReadableTime :: UTCTime -> UTCTime -> Pretty prettyHumanReadableTime now time = diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index b9e06cd6973..bfc6f72f6cc 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -5,6 +5,7 @@ module Unison.Codebase.Editor.HandleInput (loop) where -- TODO: Don't import backend +import Control.Arrow ((&&&)) import Control.Error.Util qualified as ErrorUtil import Control.Lens hiding (from) import Control.Monad.Reader (ask) @@ -96,6 +97,7 @@ import Unison.Codebase.Editor.Output.DumpNamespace qualified as Output.DN import Unison.Codebase.Editor.RemoteRepo qualified as RemoteRepo import Unison.Codebase.Editor.Slurp qualified as Slurp import Unison.Codebase.Editor.SlurpResult qualified as SlurpResult +import Unison.Codebase.Editor.StructuredArgument qualified as SA import Unison.Codebase.Editor.TodoOutput qualified as TO import Unison.Codebase.IntegrityCheck qualified as IntegrityCheck (integrityCheckFullCodebase) import Unison.Codebase.Metadata qualified as Metadata @@ -288,19 +290,22 @@ loop e = do Cli.respond $ PrintMessage pretty ShowReflogI -> do let numEntriesToShow = 500 - entries <- - Cli.runTransaction do - schLength <- Codebase.branchHashLength - Codebase.getReflog numEntriesToShow <&> fmap (first $ SCH.fromHash schLength) + (schLength, entries) <- + Cli.runTransaction $ + (,) <$> Codebase.branchHashLength <*> Codebase.getReflog numEntriesToShow let moreEntriesToLoad = length entries == numEntriesToShow let expandedEntries = List.unfoldr expandEntries (entries, Nothing, moreEntriesToLoad) - let numberedEntries = expandedEntries <&> \(_time, hash, _reason) -> "#" <> SCH.toString hash + let (shortEntries, numberedEntries) = + unzip $ + expandedEntries <&> \(time, hash, reason) -> + let ((exp, txt), sa) = ((id &&& ("#" <>) . SCH.toText) . SCH.fromHash schLength &&& SA.Namespace) hash + in ((time, exp, reason), (txt, sa)) Cli.setNumberedArgs numberedEntries - Cli.respond $ ShowReflog expandedEntries + Cli.respond $ ShowReflog shortEntries where expandEntries :: - ([Reflog.Entry SCH.ShortCausalHash Text], Maybe SCH.ShortCausalHash, Bool) -> - Maybe ((Maybe UTCTime, SCH.ShortCausalHash, Text), ([Reflog.Entry SCH.ShortCausalHash Text], Maybe SCH.ShortCausalHash, Bool)) + ([Reflog.Entry CausalHash Text], Maybe CausalHash, Bool) -> + Maybe ((Maybe UTCTime, CausalHash, Text), ([Reflog.Entry CausalHash Text], Maybe CausalHash, Bool)) expandEntries ([], Just expectedHash, moreEntriesToLoad) = if moreEntriesToLoad then Nothing @@ -786,13 +791,13 @@ loop e = do (seg, _) <- Map.toList (Branch._edits b) ] Cli.respond $ ListOfPatches $ Set.fromList patches - Cli.setNumberedArgs $ fmap (Text.unpack . Name.toText) patches + Cli.setNumberedArgs $ fmap (Name.toText &&& SA.Name) patches FindShallowI pathArg -> do Cli.Env {codebase} <- ask pathArgAbs <- Cli.resolvePath' pathArg entries <- liftIO (Backend.lsAtPath codebase Nothing pathArgAbs) - Cli.setNumberedArgs $ fmap entryToHQString entries + Cli.setNumberedArgs $ fmap (entryToHQText &&& SA.ShallowListEntry pathArg) entries pped <- Cli.currentPrettyPrintEnvDecl let suffixifiedPPE = PPED.suffixifiedPPE pped -- This used to be a delayed action which only forced the loading of the root @@ -803,19 +808,20 @@ loop e = do let buildPPE = pure suffixifiedPPE Cli.respond $ ListShallow buildPPE entries where - entryToHQString :: ShallowListEntry v Ann -> String - entryToHQString e = - fixup $ Text.unpack case e of + entryToHQText :: ShallowListEntry v Ann -> Text + entryToHQText e = + fixup $ case e of ShallowTypeEntry te -> Backend.typeEntryDisplayName te ShallowTermEntry te -> Backend.termEntryDisplayName te ShallowBranchEntry ns _ _ -> NameSegment.toEscapedText ns ShallowPatchEntry ns -> NameSegment.toEscapedText ns where - fixup s = case pathArgStr of - "" -> s - p | last p == '.' -> p ++ s - p -> p ++ "." ++ s - pathArgStr = show pathArg + fixup s = + pathArgStr + <> if Text.null pathArgStr || Text.isSuffixOf "." pathArgStr + then s + else "." <> s + pathArgStr = Text.pack $ show pathArg FindI isVerbose fscope ws -> handleFindI isVerbose fscope ws input StructuredFindI _fscope ws -> handleStructuredFindI ws StructuredFindReplaceI ws -> handleStructuredFindReplaceI ws @@ -1495,7 +1501,7 @@ handleFindI isVerbose fscope ws input = do (mapMaybe (HQ.parseTextWith anythingBeforeHash . Text.pack) qs) pure $ uniqueBy SR.toReferent srs let respondResults results = do - Cli.setNumberedArgs $ fmap (searchResultToHQString searchRoot) results + Cli.setNumberedArgs $ fmap (searchResultToHQText searchRoot &&& SA.SearchResult searchRoot) results results' <- Cli.runTransaction (Backend.loadSearchResults codebase results) Cli.respond $ ListOfDefinitions fscope suffixifiedPPE isVerbose results' results <- getResults names @@ -1550,8 +1556,8 @@ handleDependencies hq = do let types = nubOrdOn snd . Name.sortByText (HQ.toText . fst) $ (join $ fst <$> results) let terms = nubOrdOn snd . Name.sortByText (HQ.toText . fst) $ (join $ snd <$> results) Cli.setNumberedArgs $ - map (Text.unpack . Reference.toText . snd) types - <> map (Text.unpack . Reference.toText . Referent.toReference . snd) terms + map ((Reference.toText &&& SA.Ref) . snd) types + <> map ((Reference.toText &&& SA.Ref) . Referent.toReference . snd) terms Cli.respond $ ListDependencies suffixifiedPPE lds (fst <$> types) (fst <$> terms) handleDependents :: HQ.HashQualified Name -> Cli () @@ -1588,7 +1594,7 @@ handleDependents hq = do let sort = nubOrdOn snd . Name.sortByText (HQ.toText . fst) let types = sort [(n, r) | (False, n, r) <- join results] let terms = sort [(n, r) | (True, n, r) <- join results] - Cli.setNumberedArgs $ map (Text.unpack . Reference.toText . view _2) (types <> terms) + Cli.setNumberedArgs . map ((Reference.toText &&& SA.Ref) . view _2) $ types <> terms Cli.respond (ListDependents ppe lds (fst <$> types) (fst <$> terms)) handleDiffNamespaceToPatch :: Text -> DiffNamespaceToPatchInput -> Cli () @@ -1769,9 +1775,7 @@ doShowTodoOutput patch scopePath = do then Cli.respond NoConflictsOrEdits else do Cli.setNumberedArgs - ( Text.unpack . Reference.toText . view _2 - <$> fst (TO.todoFrontierDependents todo) - ) + ((Reference.toText &&& SA.Ref) . view _2 <$> fst (TO.todoFrontierDependents todo)) pped <- Cli.currentPrettyPrintEnvDecl Cli.respondNumbered $ TodoOutput pped todo @@ -1817,11 +1821,11 @@ confirmedCommand i = do loopState <- State.get pure $ Just i == (loopState ^. #lastInput) --- | restores the full hash to these search results, for _numberedArgs purposes -searchResultToHQString :: Maybe Path -> SearchResult -> String -searchResultToHQString oprefix = \case - SR.Tm' n r _ -> Text.unpack $ HQ.toText $ HQ.requalify (addPrefix <$> n) r - SR.Tp' n r _ -> Text.unpack $ HQ.toText $ HQ.requalify (addPrefix <$> n) (Referent.Ref r) +--- | restores the full hash to these search results, for _numberedArgs purposes +searchResultToHQText :: Maybe Path -> SearchResult -> Text +searchResultToHQText oprefix = \case + SR.Tm' n r _ -> HQ.toText $ HQ.requalify (addPrefix <$> n) r + SR.Tp' n r _ -> HQ.toText $ HQ.requalify (addPrefix <$> n) (Referent.Ref r) _ -> error "impossible match failure" where addPrefix :: Name -> Name diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs index 83cc5486ea1..9ad17bbcc6a 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs @@ -4,6 +4,7 @@ module Unison.Codebase.Editor.HandleInput.FindAndReplace ) where +import Control.Arrow ((&&&)) import Control.Lens hiding (at) import Control.Monad.Reader (ask) import Control.Monad.State @@ -18,6 +19,7 @@ import Unison.Cli.Pretty qualified as P import Unison.Codebase qualified as Codebase import Unison.Codebase.Branch.Names qualified as Branch import Unison.Codebase.Editor.Output +import Unison.Codebase.Editor.StructuredArgument qualified as SA import Unison.HashQualified qualified as HQ import Unison.HashQualified' qualified as HQ' import Unison.Name (Name) @@ -87,7 +89,7 @@ handleStructuredFindI rule = do ok t = pure (t, False) results0 <- traverse ok results let results = Alphabetical.sortAlphabeticallyOn fst [(hq, r) | ((hq, r), True) <- results0] - let toNumArgs = Text.unpack . Reference.toText . Referent.toReference . view _2 + let toNumArgs = (Reference.toText &&& SA.Ref) . Referent.toReference . view _2 Cli.setNumberedArgs $ map toNumArgs results Cli.respond (ListStructuredFind (fst <$> results)) diff --git a/unison-cli/src/Unison/Codebase/Editor/Output.hs b/unison-cli/src/Unison/Codebase/Editor/Output.hs index 28ec687dee7..751292ba92b 100644 --- a/unison-cli/src/Unison/Codebase/Editor/Output.hs +++ b/unison-cli/src/Unison/Codebase/Editor/Output.hs @@ -36,6 +36,7 @@ import Unison.Codebase.Editor.Output.PushPull (PushPull) import Unison.Codebase.Editor.RemoteRepo import Unison.Codebase.Editor.SlurpResult (SlurpResult (..)) import Unison.Codebase.Editor.SlurpResult qualified as SR +import Unison.Codebase.Editor.StructuredArgument (StructuredArgument) import Unison.Codebase.Editor.TodoOutput qualified as TO import Unison.Codebase.IntegrityCheck (IntegrityResult (..)) import Unison.Codebase.Patch (Patch) @@ -84,7 +85,12 @@ type ListDetailed = Bool type SourceName = Text -type NumberedArgs = [String] +-- | +-- +-- __NB__: This only temporarily holds `Text`. Until all of the inputs are +-- updated to handle `StructuredArgument`s, we need to ensure that the +-- serialization remains unchanged. +type NumberedArgs = [(Text, StructuredArgument)] type HashLength = Int diff --git a/unison-cli/src/Unison/Codebase/Editor/StructuredArgument.hs b/unison-cli/src/Unison/Codebase/Editor/StructuredArgument.hs new file mode 100644 index 00000000000..935d6ccd27a --- /dev/null +++ b/unison-cli/src/Unison/Codebase/Editor/StructuredArgument.hs @@ -0,0 +1,31 @@ +module Unison.Codebase.Editor.StructuredArgument where + +import GHC.Generics (Generic) +import U.Codebase.HashTags (CausalHash) +import Unison.Codebase.Editor.Input +import Unison.Codebase.Path (Path, Path') +import Unison.Codebase.Path qualified as Path +import Unison.HashQualified qualified as HQ +import Unison.HashQualified' qualified as HQ' +import Unison.Name (Name) +import Unison.Parser.Ann (Ann) +import Unison.Project (ProjectAndBranch, ProjectBranchName, ProjectName) +import Unison.Reference (Reference) +import Unison.Server.Backend (ShallowListEntry) +import Unison.Server.SearchResult (SearchResult) +import Unison.Symbol (Symbol) + +-- | The types that can be referenced by a numeric command argument. +data StructuredArgument + = AbsolutePath Path.Absolute + | Name Name + | HashQualified (HQ.HashQualified Name) + | Project ProjectName + | ProjectBranch (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + | Ref Reference + | Namespace CausalHash + | NameWithBranchPrefix AbsBranchId Name + | HashQualifiedWithBranchPrefix AbsBranchId (HQ'.HashQualified Name) + | ShallowListEntry Path' (ShallowListEntry Symbol Ann) + | SearchResult (Maybe Path) SearchResult + deriving (Eq, Generic, Show) diff --git a/unison-cli/src/Unison/CommandLine.hs b/unison-cli/src/Unison/CommandLine.hs index 38d53a4a8bb..41cf3ae963c 100644 --- a/unison-cli/src/Unison/CommandLine.hs +++ b/unison-cli/src/Unison/CommandLine.hs @@ -173,7 +173,7 @@ expandNumber :: NumberedArgs -> String -> [String] expandNumber numberedArgs s = case expandedNumber of Nothing -> [s] Just nums -> - [s | i <- nums, Just s <- [vargs Vector.!? (i - 1)]] + [Text.unpack (fst s) | i <- nums, Just s <- [vargs Vector.!? (i - 1)]] where vargs = Vector.fromList numberedArgs rangeRegex = "([0-9]+)-([0-9]+)" :: String diff --git a/unison-cli/src/Unison/CommandLine/OutputMessages.hs b/unison-cli/src/Unison/CommandLine/OutputMessages.hs index 3464235f1db..af5b1fa1c59 100644 --- a/unison-cli/src/Unison/CommandLine/OutputMessages.hs +++ b/unison-cli/src/Unison/CommandLine/OutputMessages.hs @@ -5,6 +5,7 @@ module Unison.CommandLine.OutputMessages where +import Control.Arrow ((&&&)) import Control.Lens hiding (at) import Control.Monad.State import Control.Monad.State.Strict qualified as State @@ -61,6 +62,8 @@ import Unison.Codebase.Editor.Output.PushPull qualified as PushPull import Unison.Codebase.Editor.RemoteRepo (ShareUserHandle (..), WriteRemoteNamespace (..), WriteShareRemoteNamespace (..)) import Unison.Codebase.Editor.RemoteRepo qualified as RemoteRepo import Unison.Codebase.Editor.SlurpResult qualified as SlurpResult +import Unison.Codebase.Editor.StructuredArgument (StructuredArgument) +import Unison.Codebase.Editor.StructuredArgument qualified as SA import Unison.Codebase.Editor.TodoOutput qualified as TO import Unison.Codebase.GitError import Unison.Codebase.IntegrityCheck (IntegrityResult (..), prettyPrintIntegrityErrors) @@ -348,7 +351,7 @@ notifyNumbered = \case ] branchHashes :: [CausalHash] branchHashes = (fst <$> reversedHistory) <> tailHashes - in (msg, displayBranchHash <$> branchHashes) + in (msg, (displayBranchHash &&& SA.Namespace) <$> branchHashes) where toSCH :: CausalHash -> ShortCausalHash toSCH h = SCH.fromHash schLength h @@ -407,7 +410,7 @@ notifyNumbered = \case ListEdits patch ppe -> showListEdits patch ppe ListProjects projects -> ( P.numberedList (map (prettyProjectName . view #name) projects), - map (Text.unpack . into @Text . view #name) projects + map ((into @Text &&& SA.Project) . view #name) projects ) ListBranches projectName branches -> ( P.columnNHeader @@ -423,7 +426,13 @@ notifyNumbered = \case ] : map (\branch -> ["", "", prettyRemoteBranchInfo branch]) remoteBranches ), - map (\(branchName, _) -> Text.unpack (into @Text (ProjectAndBranch projectName branchName))) branches + map + ( ( (into @Text . ProjectAndBranch projectName) + &&& (SA.ProjectBranch . ProjectAndBranch (pure projectName)) + ) + . fst + ) + branches ) AmbiguousSwitch project (ProjectAndBranch currentProject branch) -> ( P.wrap @@ -448,8 +457,11 @@ notifyNumbered = \case <> switch ["2"] <> " to pick one of these." ), - [ Text.unpack (Text.cons '/' (into @Text branch)), - Text.unpack (into @Text (ProjectAndBranch project (UnsafeProjectBranchName "main"))) + [ (Text.cons '/' . into @Text &&& SA.ProjectBranch . ProjectAndBranch Nothing) branch, + ( (into @Text . ProjectAndBranch project) + &&& (SA.ProjectBranch . ProjectAndBranch (pure project)) + ) + $ UnsafeProjectBranchName "main" ] ) where @@ -478,8 +490,8 @@ notifyNumbered = \case <> reset (resetArgs ["2"]) <> " to pick one of these." ), - [ Text.unpack (Text.cons '/' (into @Text branch)), - Text.unpack (into @Text (show absPath0)) + [ (Text.cons '/' . into @Text &&& SA.ProjectBranch . ProjectAndBranch Nothing) branch, + (into @Text . show &&& SA.AbsolutePath) absPath0 ] ) where @@ -515,13 +527,13 @@ notifyNumbered = \case newNextNum = nextNum + length unnumberedNames in ( newNextNum, ( nameToNum <> (Map.fromList (zip unnumberedNames [nextNum ..])), - args <> fmap Name.toText unnumberedNames + args <> unnumberedNames ) ) ) (1, (mempty, mempty)) & snd - & over (_2 . mapped) Text.unpack + & over (_2 . mapped) (Name.toText &&& SA.Name) externalDepsTable :: Map LabeledDependency (Set Name) -> [(P.Pretty P.ColorText, P.Pretty P.ColorText)] externalDepsTable = ifoldMap $ \ld dependents -> [(prettyLD ld, prettyDependents dependents)] @@ -601,7 +613,7 @@ showListEdits patch ppe = let lhsTermName = PPE.termName ppe (Referent.Ref lhsRef) -- We use the shortHash of the lhs rather than its name for numbered args, -- since its name is likely to be "historical", and won't work if passed to a ucm command. - let lhsHash = Text.unpack . ShortHash.toText . Reference.toShortHash $ lhsRef + let lhsHash = (ShortHash.toText . Reference.toShortHash &&& SA.Ref) $ lhsRef case termEdit of TermEdit.Deprecate -> do lift $ tell ([lhsHash], []) @@ -612,7 +624,7 @@ showListEdits patch ppe = TermEdit.Replace rhsRef _typing -> do n2 <- gets snd <* modify (second succ) let rhsTermName = PPE.termName ppe (Referent.Ref rhsRef) - lift $ tell ([lhsHash], [Text.unpack (HQ.toText rhsTermName)]) + lift $ tell ([lhsHash], [(HQ.toText &&& SA.HashQualified) rhsTermName]) pure ( showNum n1 <> (P.syntaxToColor . prettyHashQualified $ lhsTermName), "-> " <> showNum n2 <> (P.syntaxToColor . prettyHashQualified $ rhsTermName) @@ -626,7 +638,7 @@ showListEdits patch ppe = let lhsTypeName = PPE.typeName ppe lhsRef -- We use the shortHash of the lhs rather than its name for numbered args, -- since its name is likely to be "historical", and won't work if passed to a ucm command. - let lhsHash = Text.unpack . ShortHash.toText . Reference.toShortHash $ lhsRef + let lhsHash = (ShortHash.toText . Reference.toShortHash &&& SA.Ref) $ lhsRef case typeEdit of TypeEdit.Deprecate -> do lift $ tell ([lhsHash], []) @@ -637,7 +649,7 @@ showListEdits patch ppe = TypeEdit.Replace rhsRef -> do n2 <- gets snd <* modify (second succ) let rhsTypeName = PPE.typeName ppe rhsRef - lift $ tell ([lhsHash], [Text.unpack (HQ.toText rhsTypeName)]) + lift $ tell ([lhsHash], [(HQ.toText &&& SA.HashQualified) rhsTypeName]) pure ( showNum n1 <> (P.syntaxToColor . prettyHashQualified $ lhsTypeName), "-> " <> showNum n2 <> (P.syntaxToColor . prettyHashQualified $ rhsTypeName) @@ -1651,7 +1663,7 @@ notifyUser dir = \case prettyNamespaceKey dest <> "is already up-to-date with" <> P.group (prettyNamespaceKey src <> ".") - DumpNumberedArgs args -> pure . P.numberedList $ fmap P.string args + DumpNumberedArgs args -> pure . P.numberedList $ fmap (P.text . fst) args NoConflictsOrEdits -> pure (P.okCallout "No conflicts or edits in progress.") HelpMessage pat -> pure $ IP.showPatternHelp pat @@ -2717,7 +2729,7 @@ renderNameConflicts ppe conflictedNames = do P.lines <$> do for (Map.toList conflictedNames) $ \(name, hashes) -> do prettyConflicts <- for hashes \hash -> do - n <- addNumberedArg (Text.unpack (HQ.toText hash)) + n <- addNumberedArg $ (HQ.toText &&& SA.HashQualified) hash pure $ formatNum n <> (P.blue . P.syntaxToColor . prettyHashQualified $ hash) pure . P.wrap $ ( "The " @@ -2749,7 +2761,7 @@ renderEditConflicts ppe Patch {..} = do <> (fmap Right . Map.toList . R.toMultimap . R.filterManyDom $ _termEdits) numberedHQName :: HQ.HashQualified Name -> Numbered Pretty numberedHQName hqName = do - n <- addNumberedArg (Text.unpack (HQ.toText hqName)) + n <- addNumberedArg $ (HQ.toText &&& SA.HashQualified) hqName pure $ formatNum n <> styleHashQualified P.bold hqName formatTypeEdits :: (Reference, Set TypeEdit.TypeEdit) -> @@ -2788,9 +2800,9 @@ renderEditConflicts ppe Patch {..} = do Numbered Pretty formatConflict = either formatTypeEdits formatTermEdits -type Numbered = State.State (Int, Seq.Seq String) +type Numbered = State.State (Int, Seq.Seq (Text, StructuredArgument)) -addNumberedArg :: String -> Numbered Int +addNumberedArg :: (Text, StructuredArgument) -> Numbered Int addNumberedArg s = do (n, args) <- State.get State.put (n + 1, args Seq.|> s) @@ -2862,11 +2874,11 @@ todoOutput ppe todo = runNumbered do todoEdits :: Numbered Pretty todoEdits = do numberedTypes <- for (unscore <$> dirtyTypes) \(ref, displayObj) -> do - n <- addNumberedArg (Text.unpack (HQ.toText $ PPE.typeName ppeu ref)) + n <- addNumberedArg . (HQ.toText &&& SA.HashQualified) $ PPE.typeName ppeu ref pure $ formatNum n <> prettyDeclPair ppeu (ref, displayObj) let filteredTerms = goodTerms (unscore <$> dirtyTerms) termNumbers <- for filteredTerms \(ref, _, _) -> do - n <- addNumberedArg (Text.unpack (HQ.toText $ PPE.termName ppeu ref)) + n <- addNumberedArg . (HQ.toText &&& SA.HashQualified) $ PPE.termName ppeu ref pure $ formatNum n let formattedTerms = TypePrinter.prettySignaturesCT ppes filteredTerms numberedTerms = zipWith (<>) termNumbers formattedTerms @@ -3166,7 +3178,7 @@ showDiffNamespace sn ppe oldPath newPath OBD.BranchDiffOutput {..} = [] -> mempty x : ys -> " (" <> P.commas (x <> " updates" : ys) <> ")" pure $ n <> P.bold " patch " <> prettyName name <> message - -- 18. patch q + -- 18. patch q prettyNamePatch prefix (name, _patchDiff) = do n <- numPatch prefix name pure $ n <> P.bold " patch " <> prettyName name @@ -3271,21 +3283,21 @@ showDiffNamespace sn ppe oldPath newPath OBD.BranchDiffOutput {..} = -- DeclPrinter.prettyDeclHeader : HQ -> Either numPatch :: Input.AbsBranchId -> Name -> Numbered Pretty numPatch prefix name = - addNumberedArg' $ prefixBranchId prefix name + addNumberedArg' $ (prefixBranchId prefix &&& SA.NameWithBranchPrefix prefix) name numHQ' :: Input.AbsBranchId -> HQ'.HashQualified Name -> Referent -> Numbered Pretty numHQ' prefix hq r = - addNumberedArg' . HQ'.toStringWith (prefixBranchId prefix) . HQ'.requalify hq $ r + addNumberedArg' . (HQ'.toTextWith (prefixBranchId prefix) &&& SA.HashQualifiedWithBranchPrefix prefix) $ HQ'.requalify hq r -- E.g. - -- prefixBranchId "#abcdef" "base.List.map" -> "#abcdef.base.List.map" + -- prefixBranchId "#abcdef" "base.List.map" -> "#abcdef:.base.List.map" -- prefixBranchId ".base" "List.map" -> ".base.List.map" - prefixBranchId :: Input.AbsBranchId -> Name -> String + prefixBranchId :: Input.AbsBranchId -> Name -> Text prefixBranchId branchId name = case branchId of - Left sch -> "#" <> SCH.toString sch <> ":" <> Text.unpack (Name.toText (Name.makeAbsolute name)) - Right pathPrefix -> Text.unpack (Name.toText (Name.makeAbsolute . Path.prefixName pathPrefix $ name)) + Left sch -> "#" <> SCH.toText sch <> ":" <> Name.toText (Name.makeAbsolute name) + Right pathPrefix -> Name.toText (Name.makeAbsolute . Path.prefixName pathPrefix $ name) - addNumberedArg' :: String -> Numbered Pretty + addNumberedArg' :: (Text, StructuredArgument) -> Numbered Pretty addNumberedArg' s = case sn of ShowNumbers -> do n <- addNumberedArg s @@ -3540,7 +3552,7 @@ numberedArgsForEndangerments (PPED.unsuffixifiedPPE -> ppe) m = m & Map.elems & concatMap toList - & fmap (Text.unpack . HQ.toText . PPE.labeledRefName ppe) + & fmap ((HQ.toText &&& SA.HashQualified) . PPE.labeledRefName ppe) -- | Format and render all dependents which are endangered by references going extinct. endangeredDependentsTable :: diff --git a/unison-cli/tests/Unison/Test/Cli/Monad.hs b/unison-cli/tests/Unison/Test/Cli/Monad.hs index 3b9407da117..0edb1dc3de1 100644 --- a/unison-cli/tests/Unison/Test/Cli/Monad.hs +++ b/unison-cli/tests/Unison/Test/Cli/Monad.hs @@ -6,6 +6,8 @@ where import Control.Lens import EasyTest import Unison.Cli.Monad qualified as Cli +import Unison.Codebase.Editor.StructuredArgument qualified as SA +import Unison.Reference qualified as Reference test :: Test () test = @@ -16,13 +18,13 @@ test = Cli.runCli dummyEnv dummyLoopState do Cli.label \goto -> do Cli.label \_ -> do - Cli.setNumberedArgs ["foo"] + Cli.setNumberedArgs [SA.Ref $ Reference.ReferenceBuiltin "foo"] goto (1 :: Int) pure 2 -- test that 'goto' short-circuits, as expected expectEqual' (Cli.Success 1) r -- test that calling 'goto' doesn't lose state changes made along the way - expectEqual' ["foo"] (state ^. #numberedArgs) + expectEqual' [SA.Ref $ Reference.ReferenceBuiltin "foo"] (state ^. #numberedArgs) ok ] diff --git a/unison-cli/unison-cli.cabal b/unison-cli/unison-cli.cabal index 804f0f4ef22..43b6756ff61 100644 --- a/unison-cli/unison-cli.cabal +++ b/unison-cli/unison-cli.cabal @@ -93,6 +93,7 @@ library Unison.Codebase.Editor.Slurp Unison.Codebase.Editor.SlurpComponent Unison.Codebase.Editor.SlurpResult + Unison.Codebase.Editor.StructuredArgument Unison.Codebase.Editor.TodoOutput Unison.Codebase.Editor.UCMVersion Unison.Codebase.Editor.UriParser From 8a95c5fe52f5f4395b21bacd542080ab5ff15616 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 14 May 2024 21:18:08 -0600 Subject: [PATCH 03/10] Push `StructuredArgument`s into `InputPattern`s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This forces each `InputPattern.parse` function to serialize any `StructuredArgument` in its arguments. It’s a stop-gap that allows us to incrementally handle the structured arguments command-by-command. --- unison-cli/src/Unison/CommandLine.hs | 27 +- .../src/Unison/CommandLine/InputPattern.hs | 13 +- .../src/Unison/CommandLine/InputPatterns.hs | 548 ++++++++++-------- unison-cli/src/Unison/CommandLine/Main.hs | 7 +- 4 files changed, 332 insertions(+), 263 deletions(-) diff --git a/unison-cli/src/Unison/CommandLine.hs b/unison-cli/src/Unison/CommandLine.hs index 41cf3ae963c..2c8be9bf436 100644 --- a/unison-cli/src/Unison/CommandLine.hs +++ b/unison-cli/src/Unison/CommandLine.hs @@ -131,7 +131,7 @@ parseInput :: [String] -> -- Returns either an error message or the fully expanded arguments list and parsed input. -- If the output is `Nothing`, the user cancelled the input (e.g. ctrl-c) - IO (Either (P.Pretty CT.ColorText) (Maybe ([String], Input))) + IO (Either (P.Pretty CT.ColorText) (Maybe (InputPattern.Arguments, Input))) parseInput codebase currentPath numberedArgs patterns segments = runExceptT do let getCurrentBranch0 :: IO (Branch0 IO) getCurrentBranch0 = Branch.head <$> Codebase.getBranchAtPath codebase currentPath @@ -141,16 +141,16 @@ parseInput codebase currentPath numberedArgs patterns segments = runExceptT do [] -> throwE "" command : args -> case Map.lookup command patterns of Just pat@(InputPattern {parse, help}) -> do - let expandedNumbers :: [String] + let expandedNumbers :: InputPattern.Arguments expandedNumbers = - foldMap (expandNumber numberedArgs) args + foldMap (\arg -> maybe [Left arg] (fmap pure) $ expandNumber numberedArgs arg) args lift (fzfResolve codebase projCtx getCurrentBranch0 pat expandedNumbers) >>= \case Left (NoFZFResolverForArgumentType _argDesc) -> throwError help Left (NoFZFOptions argDesc) -> throwError (noCompletionsMessage argDesc) Left FZFCancelled -> pure Nothing Right resolvedArgs -> do parsedInput <- except . parse $ resolvedArgs - pure $ Just (command : resolvedArgs, parsedInput) + pure $ Just (Left command : resolvedArgs, parsedInput) Nothing -> throwE . warn @@ -169,11 +169,9 @@ parseInput codebase currentPath numberedArgs patterns segments = runExceptT do ] -- Expand a numeric argument like `1` or a range like `3-9` -expandNumber :: NumberedArgs -> String -> [String] -expandNumber numberedArgs s = case expandedNumber of - Nothing -> [s] - Just nums -> - [Text.unpack (fst s) | i <- nums, Just s <- [vargs Vector.!? (i - 1)]] +expandNumber :: NumberedArgs -> String -> Maybe NumberedArgs +expandNumber numberedArgs s = + (\nums -> [arg | i <- nums, Just arg <- [vargs Vector.!? (i - 1)]]) <$> expandedNumber where vargs = Vector.fromList numberedArgs rangeRegex = "([0-9]+)-([0-9]+)" :: String @@ -194,13 +192,13 @@ data FZFResolveFailure | NoFZFOptions Text {- argument description -} | FZFCancelled -fzfResolve :: Codebase IO Symbol Ann -> ProjectContext -> (IO (Branch0 IO)) -> InputPattern -> [String] -> IO (Either FZFResolveFailure [String]) +fzfResolve :: Codebase IO Symbol Ann -> ProjectContext -> (IO (Branch0 IO)) -> InputPattern -> InputPattern.Arguments -> IO (Either FZFResolveFailure InputPattern.Arguments) fzfResolve codebase projCtx getCurrentBranch pat args = runExceptT do -- We resolve args in two steps, first we check that all arguments that will require a fzf -- resolver have one, and only if so do we prompt the user to actually do a fuzzy search. -- Otherwise, we might ask the user to perform a search only to realize we don't have a resolver -- for a later arg. - argumentResolvers :: [ExceptT FZFResolveFailure IO [String]] <- + argumentResolvers :: [ExceptT FZFResolveFailure IO InputPattern.Arguments] <- (Align.align (InputPattern.args pat) args) & traverse \case This (argName, opt, InputPattern.ArgumentType {fzfResolver}) @@ -213,7 +211,7 @@ fzfResolve codebase projCtx getCurrentBranch pat args = runExceptT do These _ arg -> pure $ pure [arg] argumentResolvers & foldMapM id where - fuzzyFillArg :: InputPattern.IsOptional -> Text -> InputPattern.FZFResolver -> ExceptT FZFResolveFailure IO [String] + fuzzyFillArg :: InputPattern.IsOptional -> Text -> InputPattern.FZFResolver -> ExceptT FZFResolveFailure IO InputPattern.Arguments fuzzyFillArg opt argDesc InputPattern.FZFResolver {getOptions} = do currentBranch <- Branch.withoutTransitiveLibs <$> liftIO getCurrentBranch options <- liftIO $ getOptions codebase projCtx currentBranch @@ -224,8 +222,9 @@ fzfResolve codebase projCtx getCurrentBranch pat args = runExceptT do `whenNothingM` throwError FZFCancelled -- If the user triggered the fuzzy finder, but selected nothing, abort the command rather than continuing execution -- with no arguments. - when (null results) $ throwError FZFCancelled - pure (Text.unpack <$> results) + if null results + then throwError FZFCancelled + else pure (Left . Text.unpack <$> results) multiSelectForOptional :: InputPattern.IsOptional -> Bool multiSelectForOptional = \case diff --git a/unison-cli/src/Unison/CommandLine/InputPattern.hs b/unison-cli/src/Unison/CommandLine/InputPattern.hs index f72506bab5f..15f58eb73b7 100644 --- a/unison-cli/src/Unison/CommandLine/InputPattern.hs +++ b/unison-cli/src/Unison/CommandLine/InputPattern.hs @@ -4,8 +4,10 @@ module Unison.CommandLine.InputPattern ( InputPattern (..), + Argument, ArgumentType (..), ArgumentDescription, + Arguments, argType, FZFResolver (..), IsOptional (..), @@ -25,6 +27,7 @@ import System.Console.Haskeline qualified as Line import Unison.Auth.HTTPClient (AuthenticatedHttpClient) import Unison.Codebase (Codebase) import Unison.Codebase.Editor.Input (Input (..)) +import Unison.Codebase.Editor.StructuredArgument (StructuredArgument) import Unison.Codebase.Path as Path import Unison.CommandLine.FZFResolvers (FZFResolver (..)) import Unison.Prelude @@ -44,6 +47,14 @@ data IsOptional data Visibility = Hidden | Visible deriving (Show, Eq, Ord) +-- | An argument to a command is either a string provided by the user which +-- needs to be parsed or a numbered argument that doesn’t need to be parsed, as +-- we’ve preserved its representation (although the numbered argument could +-- still be of the wrong type, which should result in an error). +type Argument = Either String (Text, StructuredArgument) + +type Arguments = [Argument] + -- | Argument description -- It should fit grammatically into sentences like "I was expecting an argument for the " -- e.g. "namespace to merge", "definition to delete", "remote target to push to" etc. @@ -55,7 +66,7 @@ data InputPattern = InputPattern visibility :: Visibility, -- Allow hiding certain commands when debugging or work-in-progress args :: [(ArgumentDescription, IsOptional, ArgumentType)], help :: P.Pretty CT.ColorText, - parse :: [String] -> Either (P.Pretty CT.ColorText) Input + parse :: Arguments -> Either (P.Pretty CT.ColorText) Input } data ArgumentType = ArgumentType diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index 90680425142..f82e3c8dcdd 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -66,6 +66,26 @@ import Unison.Util.ColorText qualified as CT import Unison.Util.Monoid (intercalateMap) import Unison.Util.Pretty qualified as P +-- | +-- +-- __TODO__: Temporary. This may stick around in a handful of cases, but it’ll +-- have to actually serialize the `StructuredArgument` rather than +-- relying on the parallel `Text`. +unifyArgument :: I.Argument -> String +unifyArgument = either id (Text.unpack . fst) + +-- | Reversed composition, here temporarily to support the deferred parsing. +-- +-- __TODO__: Temporary. +andThen :: (a -> b) -> (b -> c) -> a -> c +andThen = flip (.) + +-- | +-- +-- __TODO__: Temporary. +unifyArguments :: [I.Argument] -> [String] +unifyArguments = fmap unifyArgument + showPatternHelp :: InputPattern -> P.Pretty CT.ColorText showPatternHelp i = P.lines @@ -107,7 +127,7 @@ mergeBuiltins = I.Hidden [("namespace", Optional, namespaceArg)] "Adds the builtins (excluding `io` and misc) to the specified namespace. Defaults to `builtin.`" - \case + $ unifyArguments `andThen` \case [] -> pure . Input.MergeBuiltinsI $ Nothing [p] -> first P.text do p <- Path.parsePath p @@ -122,7 +142,7 @@ mergeIOBuiltins = I.Hidden [("namespace", Optional, namespaceArg)] "Adds all the builtins, including `io` and misc., to the specified namespace. Defaults to `builtin.`" - \case + $ unifyArguments `andThen` \case [] -> pure . Input.MergeIOBuiltinsI $ Nothing [p] -> first P.text do p <- Path.parsePath p @@ -162,7 +182,7 @@ todo = ) ] ) - ( \case + ( unifyArguments `andThen` \case patchStr : ws -> mapLeft (warn . P.text) $ do patch <- Path.parseSplit' patchStr branch <- case ws of @@ -189,7 +209,7 @@ load = ) ] ) - ( \case + ( unifyArguments `andThen` \case [] -> pure $ Input.LoadI Nothing [file] -> pure $ Input.LoadI . Just $ file _ -> Left (I.help load) @@ -223,7 +243,7 @@ add = ( "`add` adds to the codebase all the definitions from the most recently " <> "typechecked file." ) - \ws -> pure $ Input.AddI (Set.fromList $ map (Name.unsafeParseText . Text.pack) ws) + $ pure . Input.AddI . Set.fromList . map (Name.unsafeParseText . Text.pack) . unifyArguments previewAdd :: InputPattern previewAdd = @@ -237,7 +257,7 @@ previewAdd = <> "results. Use `load` to reparse & typecheck the file if the context " <> "has changed." ) - \ws -> pure $ Input.PreviewAddI (Set.fromList $ map (Name.unsafeParseText . Text.pack) ws) + $ pure . Input.PreviewAddI . Set.fromList . map (Name.unsafeParseText . Text.pack) . unifyArguments update :: InputPattern update = @@ -284,12 +304,11 @@ updateOldNoPatch = ) ] ) - ( \case - ws -> do - pure $ - Input.UpdateI - Input.NoPatch - (Set.fromList $ map (Name.unsafeParseText . Text.pack) ws) + ( pure + . Input.UpdateI Input.NoPatch + . Set.fromList + . map (Name.unsafeParseText . Text.pack) + . unifyArguments ) updateOld :: InputPattern @@ -324,7 +343,7 @@ updateOld = ) ] ) - \case + $ unifyArguments `andThen` \case patchStr : ws -> do patch <- first P.text $ Path.parseSplit' patchStr pure $ @@ -345,7 +364,7 @@ previewUpdate = <> "typechecking results. Use `load` to reparse & typecheck the file if " <> "the context has changed." ) - \ws -> pure $ Input.PreviewUpdateI (Set.fromList $ map (Name.unsafeParseText . Text.pack) ws) + $ pure . Input.PreviewUpdateI . Set.fromList . map (Name.unsafeParseText . Text.pack) . unifyArguments patch :: InputPattern patch = @@ -373,7 +392,7 @@ patch = ] ] ) - \case + $ unifyArguments `andThen` \case patchStr : ws -> first P.text do patch <- Path.parseSplit' patchStr branch <- case ws of @@ -404,7 +423,7 @@ view = <> "not `List.map.doc` (since ? only matches 1 name segment)." ] ) - ( \case + ( unifyArguments `andThen` \case (x : xs) -> (x NE.:| xs) & traverse parseHashQualifiedName @@ -424,7 +443,7 @@ viewGlobal = "`view.global` without arguments invokes a search to select definitions to view, which requires that `fzf` can be found within your PATH." ] ) - ( \case + ( unifyArguments `andThen` \case (x : xs) -> (x NE.:| xs) & traverse parseHashQualifiedName @@ -444,7 +463,7 @@ display = "`display` without arguments invokes a search to select a definition to display, which requires that `fzf` can be found within your PATH." ] ) - \case + $ unifyArguments `andThen` \case (x : xs) -> (x NE.:| xs) & traverse parseHashQualifiedName @@ -462,7 +481,7 @@ displayTo = makeExample displayTo ["", "foo"] <> "prints a rendered version of the term `foo` to the given file." ) - \case + $ unifyArguments `andThen` \case file : (x : xs) -> (x NE.:| xs) & traverse parseHashQualifiedName @@ -481,7 +500,7 @@ docs = "`docs` without arguments invokes a search to select which definition to view documentation for, which requires that `fzf` can be found within your PATH." ] ) - ( \case + ( unifyArguments `andThen` \case x : xs -> (x NE.:| xs) & traverse Path.parseHQSplit' @@ -507,12 +526,13 @@ ui = visibility = I.Visible, args = [("definition to load", Optional, namespaceOrDefinitionArg)], help = P.wrap "`ui` opens the Local UI in the default browser.", - parse = \case - [] -> pure $ Input.UiI Path.relativeEmpty' - [path] -> first P.text $ do - p <- Path.parsePath' path - pure $ Input.UiI p - _ -> Left (I.help ui) + parse = + unifyArguments `andThen` \case + [] -> pure $ Input.UiI Path.relativeEmpty' + [path] -> first P.text $ do + p <- Path.parsePath' path + pure $ Input.UiI p + _ -> Left (I.help ui) } undo :: InputPattern @@ -535,11 +555,12 @@ viewByPrefix = "`view.recursive Foo` prints the definitions of `Foo` and `Foo.blah`." ( fmap (Input.ShowDefinitionByPrefixI Input.ConsoleLocation) . traverse parseHashQualifiedName + . unifyArguments ) sfind :: InputPattern sfind = - InputPattern "rewrite.find" ["sfind"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg parse + InputPattern "rewrite.find" ["sfind"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg $ parse . unifyArguments where parse [q] = Input.StructuredFindI (Input.FindLocal Path.empty) <$> parseHashQualifiedName q parse _ = Left "expected exactly one argument" @@ -570,7 +591,7 @@ sfind = sfindReplace :: InputPattern sfindReplace = - InputPattern "rewrite" ["sfind.replace"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg parse + InputPattern "rewrite" ["sfind.replace"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg $ parse . unifyArguments where parse [q] = Input.StructuredFindReplaceI <$> parseHashQualifiedName q parse _ = Left "expected exactly one argument" @@ -619,7 +640,7 @@ findIn' cmd mkfscope = I.Visible [("namespace", Required, namespaceArg), ("query", ZeroPlus, exactDefinitionArg)] findHelp - \case + $ unifyArguments `andThen` \case p : args -> first P.text do p <- Path.parsePath p pure (Input.FindI False (mkfscope p) args) @@ -669,7 +690,7 @@ find' cmd fscope = I.Visible [("query", ZeroPlus, exactDefinitionArg)] findHelp - (pure . Input.FindI False fscope) + (pure . Input.FindI False fscope . unifyArguments) findShallow :: InputPattern findShallow = @@ -684,7 +705,7 @@ findShallow = ("`list .foo`", "lists the '.foo' namespace.") ] ) - ( \case + ( unifyArguments `andThen` \case [] -> pure $ Input.FindShallowI Path.relativeEmpty' [path] -> first P.text $ do p <- Path.parsePath' path @@ -702,7 +723,7 @@ findVerbose = ( "`find.verbose` searches for definitions like `find`, but includes hashes " <> "and aliases in the results." ) - (pure . Input.FindI True (Input.FindLocal Path.empty)) + (pure . Input.FindI True (Input.FindLocal Path.empty) . unifyArguments) findVerboseAll :: InputPattern findVerboseAll = @@ -714,7 +735,7 @@ findVerboseAll = ( "`find.all.verbose` searches for definitions like `find.all`, but includes hashes " <> "and aliases in the results." ) - (pure . Input.FindI True (Input.FindLocalAndDeps Path.empty)) + (pure . Input.FindI True (Input.FindLocalAndDeps Path.empty) . unifyArguments) findPatch :: InputPattern findPatch = @@ -738,7 +759,7 @@ renameTerm = ("new location", Required, newNameArg) ] "`move.term foo bar` renames `foo` to `bar`." - ( \case + ( unifyArguments `andThen` \case [oldName, newName] -> first P.text do src <- Path.parseHQSplit' oldName target <- Path.parseSplit' newName @@ -759,7 +780,7 @@ moveAll = ("new location", Required, newNameArg) ] "`move foo bar` renames the term, type, and namespace foo to bar." - ( \case + ( unifyArguments `andThen` \case [oldName, newName] -> first P.text $ do src <- Path.parsePath' oldName target <- Path.parsePath' newName @@ -780,7 +801,7 @@ renameType = ("new location", Required, newNameArg) ] "`move.type foo bar` renames `foo` to `bar`." - ( \case + ( unifyArguments `andThen` \case [oldName, newName] -> first P.text do src <- Path.parseHQSplit' oldName target <- Path.parseSplit' newName @@ -828,7 +849,7 @@ deleteGen suffix queryCompletionArg target mkTarget = I.Visible [("definition to delete", OnePlus, queryCompletionArg)] info - ( \case + ( unifyArguments `andThen` \case [] -> Left . P.warnCallout $ P.wrap warn queries -> first P.text do paths <- traverse Path.parseHQSplit' queries @@ -876,7 +897,7 @@ deleteReplacement isTerm = <> str <> " - not the one in place after the edit." ) - ( \case + ( unifyArguments `andThen` \case query : patch -> do patch <- first P.text . traverse Path.parseSplit' $ listToMaybe patch q <- parseHashQualifiedName query @@ -912,11 +933,12 @@ deleteProject = P.wrapColumn2 [ ("`delete.project foo`", "deletes the local project `foo`") ], - parse = \case - [name] - | Right project <- tryInto @ProjectName (Text.pack name) -> - Right (Input.DeleteI (DeleteTarget'Project project)) - _ -> Left (showPatternHelp deleteProject) + parse = + unifyArguments `andThen` \case + [name] + | Right project <- tryInto @ProjectName (Text.pack name) -> + Right (Input.DeleteI (DeleteTarget'Project project)) + _ -> Left (showPatternHelp deleteProject) } deleteBranch :: InputPattern @@ -931,12 +953,13 @@ deleteBranch = [ ("`delete.branch foo/bar`", "deletes the branch `bar` in the project `foo`"), ("`delete.branch /bar`", "deletes the branch `bar` in the current project") ], - parse = \case - [name] -> - case tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) (Text.pack name) of - Left _ -> Left (showPatternHelp deleteBranch) - Right projectAndBranch -> Right (Input.DeleteI (DeleteTarget'ProjectBranch projectAndBranch)) - _ -> Left (showPatternHelp deleteBranch) + parse = + unifyArguments `andThen` \case + [name] -> + case tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) (Text.pack name) of + Left _ -> Left (showPatternHelp deleteBranch) + Right projectAndBranch -> Right (Input.DeleteI (DeleteTarget'ProjectBranch projectAndBranch)) + _ -> Left (showPatternHelp deleteBranch) } where suggestionsConfig = @@ -960,7 +983,7 @@ aliasTerm = I.Visible [("term to alias", Required, exactDefinitionTermQueryArg), ("alias name", Required, newNameArg)] "`alias.term foo bar` introduces `bar` with the same definition as `foo`." - \case + $ unifyArguments `andThen` \case [oldName, newName] -> first P.text do source <- Path.parseShortHashOrHQSplit' oldName target <- Path.parseSplit' newName @@ -978,7 +1001,7 @@ aliasType = I.Visible [("type to alias", Required, exactDefinitionTypeQueryArg), ("alias name", Required, newNameArg)] "`alias.type Foo Bar` introduces `Bar` with the same definition as `Foo`." - \case + $ unifyArguments `andThen` \case [oldName, newName] -> first P.text do source <- Path.parseShortHashOrHQSplit' oldName target <- Path.parseSplit' newName @@ -1004,7 +1027,7 @@ aliasMany = <> "creates aliases `.quux.foo.foo` and `.quux.bar.bar`." ] ) - \case + $ unifyArguments `andThen` \case srcs@(_ : _) Cons.:> dest -> first P.text do sourceDefinitions <- traverse Path.parseHQSplit srcs destNamespace <- Path.parsePath' dest @@ -1050,7 +1073,7 @@ cd = ] ] ) - \case + $ unifyArguments `andThen` \case [".."] -> Right Input.UpI [p] -> first P.text do p <- Path.parsePath' p @@ -1082,7 +1105,7 @@ deleteNamespace = I.Visible [("namespace to delete", Required, namespaceArg)] "`delete.namespace ` deletes the namespace `foo`" - (deleteNamespaceParser (I.help deleteNamespace) Input.Try) + (deleteNamespaceParser (I.help deleteNamespace) Input.Try . unifyArguments) deleteNamespaceForce :: InputPattern deleteNamespaceForce = @@ -1094,7 +1117,7 @@ deleteNamespaceForce = ( "`delete.namespace.force ` deletes the namespace `foo`," <> "deletion will proceed even if other code depends on definitions in foo." ) - (deleteNamespaceParser (I.help deleteNamespaceForce) Input.Force) + (deleteNamespaceParser (I.help deleteNamespaceForce) Input.Force . unifyArguments) deleteNamespaceParser :: P.Pretty CT.ColorText -> Input.Insistence -> [String] -> Either (P.Pretty CT.ColorText) Input deleteNamespaceParser helpText insistence = \case @@ -1115,7 +1138,7 @@ deletePatch = I.Visible [("patch to delete", Required, patchArg)] "`delete.patch ` deletes the patch `foo`" - \case + $ unifyArguments `andThen` \case [p] -> first P.text do p <- Path.parseSplit' p pure . Input.DeleteI $ DeleteTarget'Patch p @@ -1141,7 +1164,7 @@ copyPatch = I.Visible [("patch to copy", Required, patchArg), ("copy destination", Required, newNameArg)] "`copy.patch foo bar` copies the patch `foo` to `bar`." - \case + $ unifyArguments `andThen` \case [src, dest] -> copyPatch' src dest _ -> Left (I.help copyPatch) @@ -1153,7 +1176,7 @@ renamePatch = I.Visible [("patch", Required, patchArg), ("new location", Required, newNameArg)] "`move.patch foo bar` renames the patch `foo` to `bar`." - \case + $ unifyArguments `andThen` \case [src, dest] -> movePatch src dest _ -> Left (I.help renamePatch) @@ -1165,7 +1188,7 @@ renameBranch = I.Visible [("namespace to move", Required, namespaceArg), ("new location", Required, newNameArg)] "`move.namespace foo bar` renames the path `foo` to `bar`." - \case + $ unifyArguments `andThen` \case [src, dest] -> first P.text do src <- Path.parsePath' src dest <- Path.parsePath' dest @@ -1188,7 +1211,7 @@ history = ) ] ) - \case + $ unifyArguments `andThen` \case [src] -> first P.text do p <- Input.parseBranchId src pure $ Input.HistoryI (Just 10) (Just 10) p @@ -1216,7 +1239,7 @@ forkLocal = ) ] ) - \case + $ unifyArguments `andThen` \case [src, dest] -> do src <- Input.parseBranchId2 src dest <- parseBranchRelativePath dest @@ -1239,15 +1262,18 @@ reset = ("`reset #pvfd222s8n /topic`", "reset the branch `topic` of the current project to the causal `#pvfd222s8n`.") ] ) - ( maybeToEither (I.help reset) . \case - arg0 : restArgs -> do - arg0 <- branchIdOrProject arg0 - arg1 <- case restArgs of - [] -> pure Nothing - arg1 : [] -> Just <$> parseLooseCodeOrProject arg1 - _ -> Nothing - Just (Input.ResetI arg0 arg1) - _ -> Nothing + ( maybeToEither (I.help reset) + . ( \case + arg0 : restArgs -> do + arg0 <- branchIdOrProject arg0 + arg1 <- case restArgs of + [] -> pure Nothing + arg1 : [] -> Just <$> parseLooseCodeOrProject arg1 + _ -> Nothing + Just (Input.ResetI arg0 arg1) + _ -> Nothing + ) + . unifyArguments ) where branchIdOrProject :: @@ -1293,7 +1319,7 @@ resetRoot = ] ] ) - \case + $ unifyArguments `andThen` \case [src] -> first P.text $ do src <- Input.parseBranchId src pure $ Input.ResetRootI src @@ -1361,21 +1387,24 @@ pullImpl name aliases verbosity pullMode addendum = do explainRemote Pull ], parse = - maybeToEither (I.help self) . \case - [] -> Just $ Input.PullRemoteBranchI Input.PullSourceTarget0 SyncMode.ShortCircuit pullMode verbosity - [sourceString] -> do - source <- parsePullSource (Text.pack sourceString) - Just $ Input.PullRemoteBranchI (Input.PullSourceTarget1 source) SyncMode.ShortCircuit pullMode verbosity - [sourceString, targetString] -> do - source <- parsePullSource (Text.pack sourceString) - target <- parseLooseCodeOrProject targetString - Just $ - Input.PullRemoteBranchI - (Input.PullSourceTarget2 source target) - SyncMode.ShortCircuit - pullMode - verbosity - _ -> Nothing + maybeToEither (I.help self) + . ( \case + [] -> Just $ Input.PullRemoteBranchI Input.PullSourceTarget0 SyncMode.ShortCircuit pullMode verbosity + [sourceString] -> do + source <- parsePullSource (Text.pack sourceString) + Just $ Input.PullRemoteBranchI (Input.PullSourceTarget1 source) SyncMode.ShortCircuit pullMode verbosity + [sourceString, targetString] -> do + source <- parsePullSource (Text.pack sourceString) + target <- parseLooseCodeOrProject targetString + Just $ + Input.PullRemoteBranchI + (Input.PullSourceTarget2 source target) + SyncMode.ShortCircuit + pullMode + verbosity + _ -> Nothing + ) + . unifyArguments } pullExhaustive :: InputPattern @@ -1396,32 +1425,35 @@ pullExhaustive = <> "versions M1l and earlier. It may be extra slow!" ] ) - ( maybeToEither (I.help pullExhaustive) . \case - [] -> - Just $ - Input.PullRemoteBranchI - Input.PullSourceTarget0 - SyncMode.Complete - Input.PullWithHistory - Verbosity.Verbose - [sourceString] -> do - source <- parsePullSource (Text.pack sourceString) - Just $ - Input.PullRemoteBranchI - (Input.PullSourceTarget1 source) - SyncMode.Complete - Input.PullWithHistory - Verbosity.Verbose - [sourceString, targetString] -> do - source <- parsePullSource (Text.pack sourceString) - target <- parseLooseCodeOrProject targetString - Just $ - Input.PullRemoteBranchI - (Input.PullSourceTarget2 source target) - SyncMode.Complete - Input.PullWithHistory - Verbosity.Verbose - _ -> Nothing + ( maybeToEither (I.help pullExhaustive) + . ( \case + [] -> + Just $ + Input.PullRemoteBranchI + Input.PullSourceTarget0 + SyncMode.Complete + Input.PullWithHistory + Verbosity.Verbose + [sourceString] -> do + source <- parsePullSource (Text.pack sourceString) + Just $ + Input.PullRemoteBranchI + (Input.PullSourceTarget1 source) + SyncMode.Complete + Input.PullWithHistory + Verbosity.Verbose + [sourceString, targetString] -> do + source <- parsePullSource (Text.pack sourceString) + target <- parseLooseCodeOrProject targetString + Just $ + Input.PullRemoteBranchI + (Input.PullSourceTarget2 source target) + SyncMode.Complete + Input.PullWithHistory + Verbosity.Verbose + _ -> Nothing + ) + . unifyArguments ) debugTabCompletion :: InputPattern @@ -1436,9 +1468,7 @@ debugTabCompletion = P.wrap $ "Completions which are finished are prefixed with a * represent finished completions." ] ) - ( \inputs -> - Right $ Input.DebugTabCompletionI inputs - ) + (Right . Input.DebugTabCompletionI . unifyArguments) debugFuzzyOptions :: InputPattern debugFuzzyOptions = @@ -1455,7 +1485,7 @@ debugFuzzyOptions = P.wrap $ "or `debug.fuzzy-options merge - _`" ] ) - \case + $ unifyArguments `andThen` \case (cmd : args) -> Right $ Input.DebugFuzzyOptionsI cmd args _ -> Left (I.help debugFuzzyOptions) @@ -1508,7 +1538,7 @@ push = explainRemote Push ] ) - \args -> do + $ unifyArguments `andThen` \args -> do sourceTarget <- case args of [] -> Right Input.PushSourceTarget0 @@ -1564,7 +1594,7 @@ pushCreate = explainRemote Push ] ) - \args -> do + $ unifyArguments `andThen` \args -> do sourceTarget <- case args of [] -> Right Input.PushSourceTarget0 @@ -1599,7 +1629,7 @@ pushForce = I.Hidden [("remote destination", Optional, remoteNamespaceArg), ("local source", Optional, namespaceOrProjectBranchArg suggestionsConfig)] (P.wrap "Like `push`, but overwrites any remote namespace.") - \args -> do + $ unifyArguments `andThen` \args -> do sourceTarget <- case args of [] -> Right Input.PushSourceTarget0 @@ -1644,7 +1674,7 @@ pushExhaustive = <> "versions M1l and earlier. It may be extra slow!" ] ) - \args -> do + $ unifyArguments `andThen` \args -> do sourceTarget <- case args of [] -> Right Input.PushSourceTarget0 @@ -1686,12 +1716,15 @@ squashMerge = <> "The resulting `dest` will have (at most) 1" <> "additional history entry.", parse = - maybeToEither (I.help squashMerge) . \case - [src, dest] -> do - src <- parseLooseCodeOrProject src - dest <- parseLooseCodeOrProject dest - Just $ Input.MergeLocalBranchI src dest Branch.SquashMerge - _ -> Nothing + maybeToEither (I.help squashMerge) + . ( \case + [src, dest] -> do + src <- parseLooseCodeOrProject src + dest <- parseLooseCodeOrProject dest + Just $ Input.MergeLocalBranchI src dest Branch.SquashMerge + _ -> Nothing + ) + . unifyArguments } where suggestionsConfig = @@ -1731,15 +1764,18 @@ mergeLocal = ) ] ) - ( maybeToEither (I.help mergeLocal) . \case - [src] -> do - src <- parseLooseCodeOrProject src - Just $ Input.MergeLocalBranchI src (This Path.relativeEmpty') Branch.RegularMerge - [src, dest] -> do - src <- parseLooseCodeOrProject src - dest <- parseLooseCodeOrProject dest - Just $ Input.MergeLocalBranchI src dest Branch.RegularMerge - _ -> Nothing + ( maybeToEither (I.help mergeLocal) + . ( \case + [src] -> do + src <- parseLooseCodeOrProject src + Just $ Input.MergeLocalBranchI src (This Path.relativeEmpty') Branch.RegularMerge + [src, dest] -> do + src <- parseLooseCodeOrProject src + dest <- parseLooseCodeOrProject dest + Just $ Input.MergeLocalBranchI src dest Branch.RegularMerge + _ -> Nothing + ) + . unifyArguments ) where config = @@ -1778,7 +1814,7 @@ diffNamespace = ) ] ) - ( \case + ( unifyArguments `andThen` \case [before, after] -> first P.text do before <- Input.parseBranchId before after <- Input.parseBranchId after @@ -1812,15 +1848,18 @@ previewMergeLocal = ) ] ) - ( maybeToEither (I.help previewMergeLocal) . \case - [src] -> do - src <- parseLooseCodeOrProject src - pure $ Input.PreviewMergeLocalBranchI src (This Path.relativeEmpty') - [src, dest] -> do - src <- parseLooseCodeOrProject src - dest <- parseLooseCodeOrProject dest - pure $ Input.PreviewMergeLocalBranchI src dest - _ -> Nothing + ( maybeToEither (I.help previewMergeLocal) + . ( \case + [src] -> do + src <- parseLooseCodeOrProject src + pure $ Input.PreviewMergeLocalBranchI src (This Path.relativeEmpty') + [src, dest] -> do + src <- parseLooseCodeOrProject src + dest <- parseLooseCodeOrProject dest + pure $ Input.PreviewMergeLocalBranchI src dest + _ -> Nothing + ) + . unifyArguments ) where suggestionsConfig = @@ -1857,7 +1896,7 @@ replaceEdit f = self ) ] ) - ( \case + ( unifyArguments `andThen` \case source : target : patch -> do patch <- first P.text <$> traverse Path.parseSplit' $ listToMaybe patch sourcehq <- parseHashQualifiedName source @@ -1898,7 +1937,7 @@ edit = "`edit` without arguments invokes a search to select a definition for editing, which requires that `fzf` can be found within your PATH." ], parse = - \case + unifyArguments `andThen` \case (x : xs) -> (x NE.:| xs) & traverse parseHashQualifiedName @@ -1918,7 +1957,7 @@ editNamespace = [ "`edit.namespace` will load all terms and types contained within the current namespace into your scratch file. This includes definitions in namespaces, but excludes libraries.", "`edit.namespace ns1 ns2 ...` loads the terms and types contained within the provided namespaces." ], - parse = Right . Input.EditNamespaceI . fmap (Path.unsafeParseText . Text.pack) + parse = Right . Input.EditNamespaceI . fmap (Path.unsafeParseText . Text.pack) . unifyArguments } topicNameArg :: ArgumentType @@ -1946,7 +1985,7 @@ helpTopics = I.Visible [("topic", Optional, topicNameArg)] ("`help-topics` lists all topics and `help-topics ` shows an explanation of that topic.") - ( \case + ( unifyArguments `andThen` \case [] -> Left topics [topic] -> case Map.lookup topic helpTopicsMap of Nothing -> Left . warn $ "I don't know of that topic. Try `help-topics`." @@ -2129,7 +2168,7 @@ help = I.Visible [("command", Optional, commandNameArg)] "`help` shows general help and `help ` shows help for one command." - \case + $ unifyArguments `andThen` \case [] -> Left $ intercalateMap @@ -2191,7 +2230,7 @@ viewPatch = ) ] ) - \case + $ unifyArguments `andThen` \case [] -> Right $ Input.ListEditsI Nothing [patchStr] -> mapLeft P.text do patch <- Path.parseSplit' patchStr @@ -2206,7 +2245,7 @@ names isGlobal = I.Visible [("name or hash", Required, definitionQueryArg)] (P.wrap $ makeExample (names isGlobal) ["foo"] <> " shows the hash and all known names for `foo`.") - \case + $ unifyArguments `andThen` \case [thing] -> case HQ.parseText (Text.pack thing) of Just hq -> Right $ Input.NamesI isGlobal hq Nothing -> @@ -2225,7 +2264,7 @@ dependents = I.Visible [("definition", Required, definitionQueryArg)] "List the named dependents of the specified definition." - \case + $ unifyArguments `andThen` \case [thing] -> fmap Input.ListDependentsI $ parseHashQualifiedName thing _ -> Left (I.help dependents) dependencies = @@ -2235,7 +2274,7 @@ dependencies = I.Visible [("definition", Required, definitionQueryArg)] "List the dependencies of the specified definition." - \case + $ unifyArguments `andThen` \case [thing] -> fmap Input.ListDependenciesI $ parseHashQualifiedName thing _ -> Left (I.help dependencies) @@ -2247,7 +2286,7 @@ namespaceDependencies = I.Visible [("namespace", Optional, namespaceArg)] "List the external dependencies of the specified namespace." - \case + $ unifyArguments `andThen` \case [p] -> first P.text do p <- Path.parsePath' p pure $ Input.NamespaceDependenciesI (Just p) @@ -2302,7 +2341,7 @@ debugTerm = I.Hidden [("term", Required, exactDefinitionTermQueryArg)] "View debugging information for a given term." - ( \case + ( unifyArguments `andThen` \case [thing] -> fmap (Input.DebugTermI False) $ parseHashQualifiedName thing _ -> Left (I.help debugTerm) ) @@ -2315,7 +2354,7 @@ debugTermVerbose = I.Hidden [("term", Required, exactDefinitionTermQueryArg)] "View verbose debugging information for a given term." - ( \case + ( unifyArguments `andThen` \case [thing] -> fmap (Input.DebugTermI True) $ parseHashQualifiedName thing _ -> Left (I.help debugTermVerbose) ) @@ -2328,7 +2367,7 @@ debugType = I.Hidden [("type", Required, exactDefinitionTypeQueryArg)] "View debugging information for a given type." - ( \case + ( unifyArguments `andThen` \case [thing] -> fmap (Input.DebugTypeI) $ parseHashQualifiedName thing _ -> Left (I.help debugType) ) @@ -2376,7 +2415,7 @@ debugNameDiff = args = [("before namespace", Required, namespaceArg), ("after namespace", Required, namespaceArg)], help = P.wrap "List all name changes between two causal hashes. Does not detect patch changes.", parse = - ( \case + ( unifyArguments `andThen` \case [from, to] -> first fromString $ do fromSCH <- Input.parseShortCausalHash from toSCH <- Input.parseShortCausalHash to @@ -2435,7 +2474,7 @@ docsToHtml = ) ] ) - \case + $ unifyArguments `andThen` \case [namespacePath, destinationFilePath] -> first P.text do np <- Path.parsePath' namespacePath pure $ Input.DocsToHtmlI np destinationFilePath @@ -2454,7 +2493,7 @@ docToMarkdown = ) ] ) - \case + $ unifyArguments `andThen` \case [docNameText] -> first fromString $ do docName <- maybeToEither "Invalid name" . Name.parseText . Text.pack $ docNameText pure $ Input.DocToMarkdownI docName @@ -2476,7 +2515,7 @@ execute = ) ] ) - \case + $ unifyArguments `andThen` \case [w] -> pure $ Input.ExecuteI (Text.pack w) [] w : ws -> pure $ Input.ExecuteI (Text.pack w) ws _ -> Left $ showPatternHelp execute @@ -2491,7 +2530,7 @@ saveExecuteResult = ( "`add.run name` adds to the codebase the result of the most recent `run` command" <> "as `name`." ) - \case + $ unifyArguments `andThen` \case [w] -> pure $ Input.SaveExecuteResultI (Name.unsafeParseText (Text.pack w)) _ -> Left $ showPatternHelp saveExecuteResult @@ -2508,9 +2547,10 @@ ioTest = "Runs `!mytest`, where `mytest` is a delayed test that can use the `IO` and `Exception` abilities." ) ], - parse = \case - [thing] -> fmap Input.IOTestI $ parseHashQualifiedName thing - _ -> Left $ showPatternHelp ioTest + parse = + unifyArguments `andThen` \case + [thing] -> fmap Input.IOTestI $ parseHashQualifiedName thing + _ -> Left $ showPatternHelp ioTest } ioTestAll :: InputPattern @@ -2546,7 +2586,7 @@ makeStandalone = ) ] ) - \case + $ unifyArguments `andThen` \case [main, file] -> Input.MakeStandaloneI file <$> parseHashQualifiedName main _ -> Left $ showPatternHelp makeStandalone @@ -2564,7 +2604,7 @@ runScheme = ) ] ) - \case + $ unifyArguments `andThen` \case main : args -> Right $ Input.ExecuteSchemeI (Text.pack main) args _ -> Left $ showPatternHelp runScheme @@ -2583,7 +2623,7 @@ compileScheme = ) ] ) - \case + $ unifyArguments `andThen` \case [main, file] -> Input.CompileSchemeI (Text.pack file) <$> parseHashQualifiedName main _ -> Left $ showPatternHelp compileScheme @@ -2606,7 +2646,7 @@ createAuthor = <> backtick (P.group ("metadata.copyrightHolders" <> ".")) ) ) - ( \case + ( unifyArguments `andThen` \case symbolStr : authorStr@(_ : _) -> first P.text do symbol <- Megaparsec.runParser (Megaparsec.withParsecT (fmap NameSegment.renderParseErr) NameSegment.segmentP <* Megaparsec.eof) "" symbolStr @@ -2641,7 +2681,7 @@ gist = <> "`push.create` of the current namespace to your Unison Share codebase wherever you like!" ] ) - ( \case + ( unifyArguments `andThen` \case [repoString] -> do repo <- parseWriteGitRepo "gist git repo" repoString pure (Input.GistI (Input.GistInput repo)) @@ -2688,14 +2728,15 @@ diffNamespaceToPatch = visibility = I.Visible, args = [], help = P.wrap "Create a patch from a namespace diff.", - parse = \case - [branchId1, branchId2, patch] -> - mapLeft P.text do - branchId1 <- Input.parseBranchId branchId1 - branchId2 <- Input.parseBranchId branchId2 - patch <- Path.parseSplit' patch - pure (Input.DiffNamespaceToPatchI Input.DiffNamespaceToPatchInput {branchId1, branchId2, patch}) - _ -> Left (showPatternHelp diffNamespaceToPatch) + parse = + unifyArguments `andThen` \case + [branchId1, branchId2, patch] -> + mapLeft P.text do + branchId1 <- Input.parseBranchId branchId1 + branchId2 <- Input.parseBranchId branchId2 + patch <- Path.parseSplit' patch + pure (Input.DiffNamespaceToPatchI Input.DiffNamespaceToPatchInput {branchId1, branchId2, patch}) + _ -> Left (showPatternHelp diffNamespaceToPatch) } projectCreate :: InputPattern @@ -2710,12 +2751,13 @@ projectCreate = [ ("`project.create`", "creates a project with a random name"), ("`project.create foo`", "creates a project named `foo`") ], - parse = \case - [name] -> - case tryInto @ProjectName (Text.pack name) of - Left _ -> Left "Invalid project name." - Right name1 -> Right (Input.ProjectCreateI True (Just name1)) - _ -> Right (Input.ProjectCreateI True Nothing) + parse = + unifyArguments `andThen` \case + [name] -> + case tryInto @ProjectName (Text.pack name) of + Left _ -> Left "Invalid project name." + Right name1 -> Right (Input.ProjectCreateI True (Just name1)) + _ -> Right (Input.ProjectCreateI True Nothing) } projectCreateEmptyInputPattern :: InputPattern @@ -2730,12 +2772,13 @@ projectCreateEmptyInputPattern = [ ("`project.create-empty`", "creates an empty project with a random name"), ("`project.create-empty foo`", "creates an empty project named `foo`") ], - parse = \case - [name] -> - case tryInto @ProjectName (Text.pack name) of - Left _ -> Left "Invalid project name." - Right name1 -> Right (Input.ProjectCreateI False (Just name1)) - _ -> Right (Input.ProjectCreateI False Nothing) + parse = + unifyArguments `andThen` \case + [name] -> + case tryInto @ProjectName (Text.pack name) of + Left _ -> Left "Invalid project name." + Right name1 -> Right (Input.ProjectCreateI False (Just name1)) + _ -> Right (Input.ProjectCreateI False Nothing) } projectRenameInputPattern :: InputPattern @@ -2749,9 +2792,10 @@ projectRenameInputPattern = P.wrapColumn2 [ ("`project.rename foo`", "renames the current project to `foo`") ], - parse = \case - [nameString] | Right name <- tryInto (Text.pack nameString) -> Right (Input.ProjectRenameI name) - _ -> Left (showPatternHelp projectRenameInputPattern) + parse = + unifyArguments `andThen` \case + [nameString] | Right name <- tryInto (Text.pack nameString) -> Right (Input.ProjectRenameI name) + _ -> Left (showPatternHelp projectRenameInputPattern) } projectSwitch :: InputPattern @@ -2768,12 +2812,13 @@ projectSwitch = ("`switch foo/`", "switches to the last branch you visited in the project `foo`"), ("`switch /bar`", "switches to the branch `bar` in the current project") ], - parse = \case - [name] -> - case tryInto @ProjectAndBranchNames (Text.pack name) of - Left _ -> Left (showPatternHelp projectSwitch) - Right projectAndBranch -> Right (Input.ProjectSwitchI projectAndBranch) - _ -> Left (showPatternHelp projectSwitch) + parse = + unifyArguments `andThen` \case + [name] -> + case tryInto @ProjectAndBranchNames (Text.pack name) of + Left _ -> Left (showPatternHelp projectSwitch) + Right projectAndBranch -> Right (Input.ProjectSwitchI projectAndBranch) + _ -> Left (showPatternHelp projectSwitch) } where suggestionsConfig = @@ -2806,10 +2851,11 @@ branchesInputPattern = [ ("`branches`", "lists all branches in the current project"), ("`branches foo", "lists all branches in the project `foo`") ], - parse = \case - [] -> Right (Input.BranchesI Nothing) - [nameString] | Right name <- tryFrom (Text.pack nameString) -> Right (Input.BranchesI (Just name)) - _ -> Left (showPatternHelp branchesInputPattern) + parse = + unifyArguments `andThen` \case + [] -> Right (Input.BranchesI Nothing) + [nameString] | Right name <- tryFrom (Text.pack nameString) -> Right (Input.BranchesI (Just name)) + _ -> Left (showPatternHelp branchesInputPattern) } branchInputPattern :: InputPattern @@ -2829,21 +2875,24 @@ branchInputPattern = ("`branch .bar foo`", "forks the path `.bar` of the current project to a new branch `foo`") ], parse = - maybeToEither (showPatternHelp branchInputPattern) . \case - [source0, name] -> do - source <- parseLooseCodeOrProject source0 - projectAndBranch <- - Text.pack name - & tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) - & eitherToMaybe - Just (Input.BranchI (Input.BranchSourceI'LooseCodeOrProject source) projectAndBranch) - [name] -> do - projectAndBranch <- - Text.pack name - & tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) - & eitherToMaybe - Just (Input.BranchI Input.BranchSourceI'CurrentContext projectAndBranch) - _ -> Nothing + maybeToEither (showPatternHelp branchInputPattern) + . ( \case + [source0, name] -> do + source <- parseLooseCodeOrProject source0 + projectAndBranch <- + Text.pack name + & tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + & eitherToMaybe + Just (Input.BranchI (Input.BranchSourceI'LooseCodeOrProject source) projectAndBranch) + [name] -> do + projectAndBranch <- + Text.pack name + & tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + & eitherToMaybe + Just (Input.BranchI Input.BranchSourceI'CurrentContext projectAndBranch) + _ -> Nothing + ) + . unifyArguments } where newBranchNameArg = @@ -2867,12 +2916,13 @@ branchEmptyInputPattern = visibility = I.Visible, args = [], help = P.wrap "Create a new empty branch.", - parse = \case - [name] -> - first (\_ -> showPatternHelp branchEmptyInputPattern) do - projectAndBranch <- tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) (Text.pack name) - Right (Input.BranchI Input.BranchSourceI'Empty projectAndBranch) - _ -> Left (showPatternHelp branchEmptyInputPattern) + parse = + unifyArguments `andThen` \case + [name] -> + first (\_ -> showPatternHelp branchEmptyInputPattern) do + projectAndBranch <- tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) (Text.pack name) + Right (Input.BranchI Input.BranchSourceI'Empty projectAndBranch) + _ -> Left (showPatternHelp branchEmptyInputPattern) } branchRenameInputPattern :: InputPattern @@ -2886,9 +2936,10 @@ branchRenameInputPattern = P.wrapColumn2 [ ("`branch.rename foo`", "renames the current branch to `foo`") ], - parse = \case - [nameString] | Right name <- tryInto (Text.pack nameString) -> Right (Input.BranchRenameI name) - _ -> Left (showPatternHelp branchRenameInputPattern) + parse = + unifyArguments `andThen` \case + [nameString] | Right name <- tryInto (Text.pack nameString) -> Right (Input.BranchRenameI name) + _ -> Left (showPatternHelp branchRenameInputPattern) } clone :: InputPattern @@ -2921,15 +2972,18 @@ clone = ) ], parse = - maybe (Left (showPatternHelp clone)) Right . \case - [remoteNamesString] -> do - remoteNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack remoteNamesString)) - Just (Input.CloneI remoteNames Nothing) - [remoteNamesString, localNamesString] -> do - remoteNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack remoteNamesString)) - localNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack localNamesString)) - Just (Input.CloneI remoteNames (Just localNames)) - _ -> Nothing + maybe (Left (showPatternHelp clone)) Right + . ( \case + [remoteNamesString] -> do + remoteNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack remoteNamesString)) + Just (Input.CloneI remoteNames Nothing) + [remoteNamesString, localNamesString] -> do + remoteNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack remoteNamesString)) + localNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack localNamesString)) + Just (Input.CloneI remoteNames (Just localNames)) + _ -> Nothing + ) + . unifyArguments } releaseDraft :: InputPattern @@ -2940,9 +2994,10 @@ releaseDraft = visibility = I.Visible, args = [], help = P.wrap "Draft a release.", - parse = \case - [tryInto @Semver . Text.pack -> Right semver] -> Right (Input.ReleaseDraftI semver) - _ -> Left (showPatternHelp releaseDraft) + parse = + unifyArguments `andThen` \case + [tryInto @Semver . Text.pack -> Right semver] -> Right (Input.ReleaseDraftI semver) + _ -> Left (showPatternHelp releaseDraft) } upgrade :: InputPattern @@ -2956,11 +3011,14 @@ upgrade = P.wrap $ "`upgrade old new` upgrades library dependency `lib.old` to `lib.new`, and, if successful, deletes `lib.old`.", parse = - maybeToEither (I.help upgrade) . \args -> do - [oldString, newString] <- Just args - old <- parseRelativeNameSegment oldString - new <- parseRelativeNameSegment newString - Just (Input.UpgradeI old new) + maybeToEither (I.help upgrade) + . ( \args -> do + [oldString, newString] <- Just args + old <- parseRelativeNameSegment oldString + new <- parseRelativeNameSegment newString + Just (Input.UpgradeI old new) + ) + . unifyArguments } where parseRelativeNameSegment :: String -> Maybe NameSegment diff --git a/unison-cli/src/Unison/CommandLine/Main.hs b/unison-cli/src/Unison/CommandLine/Main.hs index 0e948b5da02..18a0c8f9c86 100644 --- a/unison-cli/src/Unison/CommandLine/Main.hs +++ b/unison-cli/src/Unison/CommandLine/Main.hs @@ -113,10 +113,11 @@ getUserInput codebase authHTTPClient currentPath numberedArgs = -- Ctrl-c or some input cancel, re-run the prompt go Right (Just (expandedArgs, i)) -> do - let expandedArgsStr = unwords expandedArgs - when (expandedArgs /= ws) $ do + let expandedArgs' = IP.unifyArguments expandedArgs + expandedArgsStr = unwords expandedArgs' + when (expandedArgs' /= ws) $ do liftIO . putStrLn $ fullPrompt <> expandedArgsStr - Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ unwords expandedArgs + Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ expandedArgsStr pure i settings :: Line.Settings IO settings = From 0a94308d625a413e61ee190258ecf188479fe057 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Wed, 15 May 2024 23:21:24 -0600 Subject: [PATCH 04/10] Have `InputPattern`s handle `NumberedRef` This converts the commands to accept structured numbered arguments, rather than turning them all into strings. --- .../src/Unison/Codebase/Editor/HandleInput.hs | 12 +- .../src/Unison/CommandLine/InputPatterns.hs | 1646 +++++++++++------ unison-cli/src/Unison/CommandLine/Main.hs | 2 +- 3 files changed, 1032 insertions(+), 628 deletions(-) diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index bfc6f72f6cc..4886caeb481 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -1501,7 +1501,7 @@ handleFindI isVerbose fscope ws input = do (mapMaybe (HQ.parseTextWith anythingBeforeHash . Text.pack) qs) pure $ uniqueBy SR.toReferent srs let respondResults results = do - Cli.setNumberedArgs $ fmap (searchResultToHQText searchRoot &&& SA.SearchResult searchRoot) results + Cli.setNumberedArgs $ fmap (HQ.toText . IP.searchResultToHQ searchRoot &&& SA.SearchResult searchRoot) results results' <- Cli.runTransaction (Backend.loadSearchResults codebase results) Cli.respond $ ListOfDefinitions fscope suffixifiedPPE isVerbose results' results <- getResults names @@ -1821,16 +1821,6 @@ confirmedCommand i = do loopState <- State.get pure $ Just i == (loopState ^. #lastInput) ---- | restores the full hash to these search results, for _numberedArgs purposes -searchResultToHQText :: Maybe Path -> SearchResult -> Text -searchResultToHQText oprefix = \case - SR.Tm' n r _ -> HQ.toText $ HQ.requalify (addPrefix <$> n) r - SR.Tp' n r _ -> HQ.toText $ HQ.requalify (addPrefix <$> n) (Referent.Ref r) - _ -> error "impossible match failure" - where - addPrefix :: Name -> Name - addPrefix = maybe id Path.prefixName2 oprefix - -- return `name` and `name....` _searchBranchPrefix :: Branch m -> Name -> [SearchResult] _searchBranchPrefix b n = case Path.unsnoc (Path.fromName n) of diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index f82e3c8dcdd..ebae14662ad 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -20,7 +20,7 @@ import System.Console.Haskeline.Completion (Completion (Completion)) import System.Console.Haskeline.Completion qualified as Haskeline import System.Console.Haskeline.Completion qualified as Line import Text.Megaparsec qualified as Megaparsec -import Text.Megaparsec.Internal qualified as Megaparsec (withParsecT) +import U.Codebase.HashTags (CausalHash (..)) import U.Codebase.Sqlite.DbId (ProjectBranchId, ProjectId) import U.Codebase.Sqlite.Project qualified as Sqlite import U.Codebase.Sqlite.Queries qualified as Queries @@ -35,23 +35,30 @@ import Unison.Codebase.Editor.Input qualified as Input import Unison.Codebase.Editor.Output.PushPull (PushPull (Pull, Push)) import Unison.Codebase.Editor.Output.PushPull qualified as PushPull import Unison.Codebase.Editor.RemoteRepo (ReadRemoteNamespace, WriteGitRepo, WriteRemoteNamespace) +import Unison.Codebase.Editor.RemoteRepo qualified as RemoteRepo import Unison.Codebase.Editor.SlurpResult qualified as SR +import Unison.Codebase.Editor.StructuredArgument (StructuredArgument) +import Unison.Codebase.Editor.StructuredArgument qualified as SA import Unison.Codebase.Editor.UriParser (readRemoteNamespaceParser) import Unison.Codebase.Editor.UriParser qualified as UriParser +import Unison.Codebase.Path (Path) import Unison.Codebase.Path qualified as Path import Unison.Codebase.Path.Parse qualified as Path import Unison.Codebase.PushBehavior qualified as PushBehavior +import Unison.Codebase.ShortCausalHash (ShortCausalHash) +import Unison.Codebase.ShortCausalHash qualified as SCH import Unison.Codebase.SyncMode qualified as SyncMode import Unison.Codebase.Verbosity (Verbosity) import Unison.Codebase.Verbosity qualified as Verbosity import Unison.CommandLine -import Unison.CommandLine.BranchRelativePath (parseBranchRelativePath, parseIncrementalBranchRelativePath) +import Unison.CommandLine.BranchRelativePath (BranchRelativePath (..), parseBranchRelativePath, parseIncrementalBranchRelativePath) import Unison.CommandLine.BranchRelativePath qualified as BranchRelativePath import Unison.CommandLine.Completion import Unison.CommandLine.FZFResolvers qualified as Resolvers import Unison.CommandLine.InputPattern (ArgumentType (..), InputPattern (InputPattern), IsOptional (..), unionSuggestions) import Unison.CommandLine.InputPattern qualified as I import Unison.HashQualified qualified as HQ +import Unison.HashQualified' qualified as HQ' import Unison.Name (Name) import Unison.Name qualified as Name import Unison.NameSegment (NameSegment) @@ -59,13 +66,23 @@ import Unison.NameSegment qualified as NameSegment import Unison.Prelude import Unison.Project (ProjectAndBranch (..), ProjectAndBranchNames (..), ProjectBranchName, ProjectBranchNameOrLatestRelease (..), ProjectBranchSpecifier (..), ProjectName, Semver) import Unison.Project.Util (ProjectContext (..), projectContextFromPath) +import Unison.Reference qualified as Reference +import Unison.Referent qualified as Referent +import Unison.Server.SearchResult (SearchResult) +import Unison.Server.SearchResult qualified as SR +import Unison.ShortHash (ShortHash) import Unison.Syntax.HashQualified qualified as HQ (parseText) -import Unison.Syntax.Name qualified as Name (parseText, unsafeParseText) -import Unison.Syntax.NameSegment qualified as NameSegment (renderParseErr, segmentP) +import Unison.Syntax.Name qualified as Name (parseTextEither) import Unison.Util.ColorText qualified as CT import Unison.Util.Monoid (intercalateMap) import Unison.Util.Pretty qualified as P +-- | +-- +-- __FIXME__: Don’t hardcode this +schLength :: Int +schLength = 10 + -- | -- -- __TODO__: Temporary. This may stick around in a handful of cases, but it’ll @@ -74,18 +91,6 @@ import Unison.Util.Pretty qualified as P unifyArgument :: I.Argument -> String unifyArgument = either id (Text.unpack . fst) --- | Reversed composition, here temporarily to support the deferred parsing. --- --- __TODO__: Temporary. -andThen :: (a -> b) -> (b -> c) -> a -> c -andThen = flip (.) - --- | --- --- __TODO__: Temporary. -unifyArguments :: [I.Argument] -> [String] -unifyArguments = fmap unifyArgument - showPatternHelp :: InputPattern -> P.Pretty CT.ColorText showPatternHelp i = P.lines @@ -98,6 +103,51 @@ showPatternHelp i = I.help i ] +-- | restores the full hash to these search results, for _numberedArgs purposes +searchResultToHQ :: Maybe Path -> SearchResult -> HQ.HashQualified Name +searchResultToHQ oprefix = \case + SR.Tm' n r _ -> HQ.requalify (addPrefix <$> n) r + SR.Tp' n r _ -> HQ.requalify (addPrefix <$> n) (Referent.Ref r) + _ -> error "impossible match failure" + where + addPrefix :: Name -> Name + addPrefix = maybe id Path.prefixName2 oprefix + +unsupportedStructuredArgument :: + Text -> I.Argument -> Either (P.Pretty CT.ColorText) String +unsupportedStructuredArgument expected = + either + pure + (const . Left . P.text $ "can’t use a numbered argument for " <> expected) + +expectedButActually :: Text -> Text -> Text -> Text +expectedButActually expected actualValue actualType = + "Expected " + <> expected + <> ", but the numbered arg resulted in " + <> actualValue + <> ", which is " + <> actualType + <> "." + +wrongStructuredArgument :: Text -> (Text, StructuredArgument) -> Text +wrongStructuredArgument expected (actualStr, actual) = + expectedButActually + expected + actualStr + case actual of + SA.Ref _ -> "a reference" + SA.Name _ -> "a name" + SA.AbsolutePath _ -> "an absolute path" + SA.Namespace _ -> "a namespace" + SA.Project _ -> "a project" + SA.ProjectBranch _ -> "a branch" + SA.HashQualified _ -> "a hash-qualified name" + SA.NameWithBranchPrefix _ _ -> "a name" + SA.HashQualifiedWithBranchPrefix _ _ -> "a hash-qualified name" + SA.ShallowListEntry _ _ -> "an annotated symbol" + SA.SearchResult _ _ -> "a search result" + patternName :: InputPattern -> P.Pretty P.ColorText patternName = fromString . I.patternName @@ -117,7 +167,411 @@ makeExampleEOS p args = backtick (intercalateMap " " id (P.nonEmpty $ fromString (I.patternName p) : args)) <> "." helpFor :: InputPattern -> P.Pretty CT.ColorText -helpFor p = I.help p +helpFor = I.help + +handleProjectArg :: I.Argument -> Either Text ProjectName +handleProjectArg = + either + ( \name -> + first + (const $ "“" <> Text.pack name <> "” is an invalid project name") + . tryInto @ProjectName + $ Text.pack name + ) + ( \case + (_, SA.Project project) -> pure project + -- __FIXME__: Do we want to treat a project branch as a project? + (_, SA.ProjectBranch (ProjectAndBranch (Just project) _)) -> pure project + otherArgType -> Left $ wrongStructuredArgument "a path" otherArgType + ) + +handleLooseCodeOrProjectArg :: + I.Argument -> Either Text Input.LooseCodeOrProject +handleLooseCodeOrProjectArg = + either + ( maybe (Left "invalid path or project branch") pure + . parseLooseCodeOrProject + ) + ( \case + (_, SA.AbsolutePath path) -> pure . This $ Path.absoluteToPath' path + (_, SA.ProjectBranch pb) -> pure $ That pb + otherArgType -> + Left $ wrongStructuredArgument "a path or project branch" otherArgType + ) + +handleProjectAndBranchArg :: + I.Argument -> + Either Text (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) +handleProjectAndBranchArg = + either + ( \name -> + first + (const $ "couldn’t find a branch named “" <> Text.pack name <> "”") + . tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + $ Text.pack name + ) + ( \case + (_, SA.ProjectBranch pb) -> pure pb + otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType + ) + +handleHashQualifiedNameArg :: + I.Argument -> Either (P.Pretty CT.ColorText) (HQ.HashQualified Name) +handleHashQualifiedNameArg = + either + parseHashQualifiedName + ( \case + (_, SA.Name name) -> pure $ HQ.NameOnly name + (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ HQ.NameOnly name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . HQ.NameOnly . Name.makeAbsolute $ Path.prefixName prefix name + (_, SA.Ref ref) -> pure . HQ.HashOnly $ Reference.toShortHash ref + (_, SA.HashQualified hqname) -> pure hqname + (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ HQ'.toHQ hqname + (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + pure . HQ'.toHQ $ Name.makeAbsolute . Path.prefixName prefix <$> hqname + (_, SA.SearchResult mpath result) -> pure $ searchResultToHQ mpath result + otherArgType -> + Left . P.text $ wrongStructuredArgument "a hash-qualified name" otherArgType + ) + +handlePathArg :: I.Argument -> Either Text Path.Path +handlePathArg = + either + Path.parsePath + \case + (_, SA.Name name) -> pure $ Path.fromName name + (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ Path.fromName name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . Path.fromName $ Path.prefixName prefix name + otherArgType -> + Left $ wrongStructuredArgument "a relative path" otherArgType + +handlePath'Arg :: I.Argument -> Either Text Path.Path' +handlePath'Arg = + either + Path.parsePath' + ( \case + (_, SA.AbsolutePath path) -> pure $ Path.absoluteToPath' path + (_, SA.Name name) -> pure $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name + otherArgType -> Left $ wrongStructuredArgument "a namespace" otherArgType + ) + +handleNewName :: I.Argument -> Either Text Path.Split' +handleNewName = + either + Path.parseSplit' + (const . Left $ "can’t use a numbered argument for a new name") + +handleNewPath :: I.Argument -> Either Text Path.Path' +handleNewPath = + either + Path.parsePath' + (const . Left $ "can’t use a numbered argument for a new namespace") + +handleSplit'Arg :: I.Argument -> Either Text Path.Split' +handleSplit'Arg = + either + Path.parseSplit' + ( \case + (_, SA.Name name) -> pure $ Path.splitFromName' name + (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ Path.splitFromName' name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . Path.splitFromName' . Name.makeAbsolute $ Path.prefixName prefix name + otherNumArg -> Left $ wrongStructuredArgument "a split name" otherNumArg + ) + +neSnoc :: [a] -> a -> NE.NonEmpty a +neSnoc xs x = foldr NE.cons (pure x) xs + +handleProjectBranchNameArg :: I.Argument -> Either Text ProjectBranchName +handleProjectBranchNameArg = + either + (first (const "Wanted a branch name, but it wasn’t") . tryInto . Text.pack) + ( \case + (_, SA.ProjectBranch (ProjectAndBranch _ branch)) -> pure branch + otherNumArg -> Left $ wrongStructuredArgument "a branch name" otherNumArg + ) + +handleBranchIdArg :: I.Argument -> Either Text Input.BranchId +handleBranchIdArg = + either + Input.parseBranchId + ( \case + (_, SA.AbsolutePath path) -> pure . pure $ Path.absoluteToPath' path + (_, SA.Name name) -> pure . pure $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Left _) name) -> + pure . pure $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . pure . Path.fromName' . Name.makeAbsolute $ + Path.prefixName prefix name + (_, SA.Namespace hash) -> pure . Left $ SCH.fromHash schLength hash + otherNumArg -> Left $ wrongStructuredArgument "a branch id" otherNumArg + ) + +handleBranchIdOrProjectArg :: + I.Argument -> + Either + Text + ( These + Input.BranchId + (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + ) +handleBranchIdOrProjectArg = + either + ( maybe (Left "Expected a branch or project, but it’s not") pure + . branchIdOrProject + ) + ( \case + (_, SA.Namespace hash) -> + pure . This . Left $ SCH.fromHash schLength hash + (_, SA.AbsolutePath path) -> + pure . This . pure $ Path.absoluteToPath' path + (_, SA.Name name) -> pure . This . pure $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Left _) name) -> + pure . This . pure $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . This . pure . Path.fromName' . Name.makeAbsolute $ + Path.prefixName prefix name + (_, SA.ProjectBranch pb) -> pure $ pure pb + otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType + ) + where + branchIdOrProject :: + String -> + Maybe + ( These + Input.BranchId + (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + ) + branchIdOrProject str = + let branchIdRes = Input.parseBranchId str + projectRes = + tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + (Text.pack str) + in case (branchIdRes, projectRes) of + (Left _, Left _) -> Nothing + (Left _, Right pr) -> Just (That pr) + (Right bid, Left _) -> Just (This bid) + (Right bid, Right pr) -> Just (These bid pr) + +handleBranchId2Arg :: + I.Argument -> Either (P.Pretty P.ColorText) (Either ShortCausalHash BranchRelativePath) +handleBranchId2Arg = + either + Input.parseBranchId2 + ( \case + (_, SA.Namespace hash) -> pure . Left $ SCH.fromHash schLength hash + (_, SA.AbsolutePath path) -> + pure . pure . LoosePath $ Path.absoluteToPath' path + (_, SA.Name name) -> pure . pure . LoosePath $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Left _) name) -> + pure . pure . LoosePath $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . pure . LoosePath . Path.fromName' . Name.makeAbsolute $ + Path.prefixName prefix name + (_, SA.ProjectBranch (ProjectAndBranch mproject branch)) -> + pure . pure . BranchRelative . This $ + maybe (Left branch) (pure . (,branch)) mproject + otherNumArg -> + Left . P.text $ wrongStructuredArgument "a branch id" otherNumArg + ) + +handleBranchRelativePathArg :: + I.Argument -> Either (P.Pretty P.ColorText) BranchRelativePath +handleBranchRelativePathArg = + either + parseBranchRelativePath + ( \case + (_, SA.AbsolutePath path) -> pure . LoosePath $ Path.absoluteToPath' path + (_, SA.Name name) -> pure . LoosePath $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Left _) name) -> + pure . LoosePath $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . LoosePath . Path.fromName' . Name.makeAbsolute $ + Path.prefixName prefix name + (_, SA.ProjectBranch (ProjectAndBranch mproject branch)) -> + pure . BranchRelative . This $ + maybe (Left branch) (pure . (,branch)) mproject + otherNumArg -> + Left . P.text $ wrongStructuredArgument "a branch id" otherNumArg + ) + +hqNameToSplit' :: HQ.HashQualified Name -> Either Text Path.HQSplit' +hqNameToSplit' = \case + HQ.HashOnly _ -> Left "Only have a hash" + HQ.NameOnly name -> pure . fmap HQ'.NameOnly $ Path.splitFromName' name + HQ.HashQualified name hash -> pure . fmap (`HQ'.HashQualified` hash) $ Path.splitFromName' name + +hqNameToSplit :: HQ.HashQualified Name -> Either Text Path.HQSplit +hqNameToSplit = \case + HQ.HashOnly _ -> Left "Only have a hash" + HQ.NameOnly name -> pure . fmap HQ'.NameOnly $ Path.splitFromName name + HQ.HashQualified name hash -> pure . fmap (`HQ'.HashQualified` hash) $ Path.splitFromName name + +hq'NameToSplit' :: HQ'.HashQualified Name -> Path.HQSplit' +hq'NameToSplit' = \case + HQ'.NameOnly name -> HQ'.NameOnly <$> Path.splitFromName' name + HQ'.HashQualified name hash -> flip HQ'.HashQualified hash <$> Path.splitFromName' name + +hq'NameToSplit :: HQ'.HashQualified Name -> Path.HQSplit +hq'NameToSplit = \case + HQ'.NameOnly name -> HQ'.NameOnly <$> Path.splitFromName name + HQ'.HashQualified name hash -> flip HQ'.HashQualified hash <$> Path.splitFromName name + +handleHashQualifiedSplit'Arg :: I.Argument -> Either Text Path.HQSplit' +handleHashQualifiedSplit'Arg = + either + Path.parseHQSplit' + ( \case + (_, SA.HashQualified name) -> hqNameToSplit' name + (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ hq'NameToSplit' hqname + (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + pure . hq'NameToSplit' $ Name.makeAbsolute . Path.prefixName prefix <$> hqname + (_, SA.SearchResult mpath result) -> hqNameToSplit' $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg + ) + +handleHashQualifiedSplitArg :: I.Argument -> Either Text Path.HQSplit +handleHashQualifiedSplitArg = + either + Path.parseHQSplit + ( \case + (_, SA.HashQualified name) -> hqNameToSplit name + (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ hq'NameToSplit hqname + (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + pure . hq'NameToSplit $ Name.makeAbsolute . Path.prefixName prefix <$> hqname + (_, SA.SearchResult mpath result) -> hqNameToSplit $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a relative name" otherNumArg + ) + +handleShortCausalHashArg :: I.Argument -> Either Text ShortCausalHash +handleShortCausalHashArg = + either + (first Text.pack . Input.parseShortCausalHash) + ( \case + (_, SA.Namespace hash) -> pure $ SCH.fromHash schLength hash + otherNumArg -> Left $ wrongStructuredArgument "a causal hash" otherNumArg + ) + +handleShortHashOrHQSplit'Arg :: + I.Argument -> Either Text (Either ShortHash Path.HQSplit') +handleShortHashOrHQSplit'Arg = + either + Path.parseShortHashOrHQSplit' + ( \case + (_, SA.Ref ref) -> pure $ Left $ Reference.toShortHash ref + (_, SA.HashQualified name) -> pure <$> hqNameToSplit' name + (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure . pure $ hq'NameToSplit' hqname + (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + pure . pure $ hq'NameToSplit' (Name.makeAbsolute . Path.prefixName prefix <$> hqname) + (_, SA.SearchResult mpath result) -> + fmap pure . hqNameToSplit' $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a hash or name" otherNumArg + ) + +handleRelativeNameSegmentArg :: I.Argument -> Either Text NameSegment +handleRelativeNameSegmentArg arg = do + name <- handleNameArg arg + let (segment NE.:| tail) = Name.reverseSegments name + if Name.isRelative name && null tail + then pure segment + else Left "Wanted a single relative name segment, but it wasn’t." + +handleNameArg :: I.Argument -> Either Text Name +handleNameArg = + either + (Name.parseTextEither . Text.pack) + ( \case + (_, SA.Name name) -> pure name + (_, SA.NameWithBranchPrefix (Left _) name) -> pure name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . Name.makeAbsolute $ Path.prefixName prefix name + (_, SA.HashQualified hqname) -> + maybe (Left "can’t find a name from the numbered arg") pure $ HQ.toName hqname + (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ HQ'.toName hqname + (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + pure . Name.makeAbsolute . Path.prefixName prefix $ HQ'.toName hqname + (_, SA.SearchResult mpath result) -> + maybe (Left "can’t find a name from the numbered arg") pure + . HQ.toName + $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg + ) + +handlePullSourceArg :: + I.Argument -> + Either + Text + (ReadRemoteNamespace (These ProjectName ProjectBranchNameOrLatestRelease)) +handlePullSourceArg = + either + (maybe (Left "not a pull source") pure . parsePullSource . Text.pack) + ( \case + (_, SA.Project project) -> + pure . RemoteRepo.ReadShare'ProjectBranch $ This project + (_, SA.ProjectBranch (ProjectAndBranch project branch)) -> + pure . RemoteRepo.ReadShare'ProjectBranch . maybe That These project $ + ProjectBranchNameOrLatestRelease'Name branch + otherNumArg -> + Left $ wrongStructuredArgument "a source to pull from" otherNumArg + ) + +handlePushTargetArg :: + I.Argument -> + Either Text (WriteRemoteNamespace (These ProjectName ProjectBranchName)) +handlePushTargetArg = + either + ( maybe (Left "Wanted a source to push from, but this ain’t it.") pure + . parsePushTarget + ) + ( fmap RemoteRepo.WriteRemoteProjectBranch + . \case + (_, SA.Project project) -> pure $ This project + (_, SA.ProjectBranch (ProjectAndBranch project branch)) -> + pure $ maybe That These project branch + otherNumArg -> + Left $ wrongStructuredArgument "a source to push from" otherNumArg + ) + +handlePushSourceArg :: I.Argument -> Either Text Input.PushSource +handlePushSourceArg = + either + ( maybe (Left "Wanted a source to push from, but this ain’t it.") pure + . parsePushSource + ) + ( \case + (_, SA.AbsolutePath path) -> pure . Input.PathySource $ Path.absoluteToPath' path + (_, SA.Name name) -> pure . Input.PathySource $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Left _) name) -> + pure . Input.PathySource $ Path.fromName' name + (_, SA.NameWithBranchPrefix (Right prefix) name) -> + pure . Input.PathySource . Path.fromName' . Name.makeAbsolute $ + Path.prefixName prefix name + (_, SA.Project project) -> pure . Input.ProjySource $ This project + (_, SA.ProjectBranch (ProjectAndBranch project branch)) -> + pure . Input.ProjySource . maybe That These project $ branch + otherNumArg -> + Left $ wrongStructuredArgument "a source to push from" otherNumArg + ) + +handleProjectAndBranchNamesArg :: I.Argument -> Either Text ProjectAndBranchNames +handleProjectAndBranchNamesArg = + either + ( first (const "The argument wasn’t a project or branch") + . tryInto @ProjectAndBranchNames + . Text.pack + ) + ( fmap ProjectAndBranchNames'Unambiguous . \case + (_, SA.Project project) -> pure $ This project + (_, SA.ProjectBranch (ProjectAndBranch mproj branch)) -> + pure $ maybe That These mproj branch + otherNumArg -> + Left $ wrongStructuredArgument "a project or branch" otherNumArg + ) mergeBuiltins :: InputPattern mergeBuiltins = @@ -127,11 +581,9 @@ mergeBuiltins = I.Hidden [("namespace", Optional, namespaceArg)] "Adds the builtins (excluding `io` and misc) to the specified namespace. Defaults to `builtin.`" - $ unifyArguments `andThen` \case + $ \case [] -> pure . Input.MergeBuiltinsI $ Nothing - [p] -> first P.text do - p <- Path.parsePath p - pure . Input.MergeBuiltinsI $ Just p + [p] -> bimap P.text (Input.MergeBuiltinsI . Just) $ handlePathArg p _ -> Left (I.help mergeBuiltins) mergeIOBuiltins :: InputPattern @@ -142,11 +594,9 @@ mergeIOBuiltins = I.Hidden [("namespace", Optional, namespaceArg)] "Adds all the builtins, including `io` and misc., to the specified namespace. Defaults to `builtin.`" - $ unifyArguments `andThen` \case + \case [] -> pure . Input.MergeIOBuiltinsI $ Nothing - [p] -> first P.text do - p <- Path.parsePath p - pure . Input.MergeIOBuiltinsI $ Just p + [p] -> bimap P.text (Input.MergeIOBuiltinsI . Just) $ handlePathArg p _ -> Left (I.help mergeBuiltins) updateBuiltins :: InputPattern @@ -182,12 +632,12 @@ todo = ) ] ) - ( unifyArguments `andThen` \case - patchStr : ws -> mapLeft (warn . P.text) $ do - patch <- Path.parseSplit' patchStr + ( \case + patchStr : ws -> first (warn . P.text) $ do + patch <- handleSplit'Arg patchStr branch <- case ws of [] -> pure Path.relativeEmpty' - [pathStr] -> Path.parsePath' pathStr + [pathStr] -> handlePath'Arg pathStr _ -> Left "`todo` just takes a patch and one optional namespace" Right $ Input.TodoI (Just patch) branch [] -> Right $ Input.TodoI Nothing Path.relativeEmpty' @@ -209,9 +659,11 @@ load = ) ] ) - ( unifyArguments `andThen` \case + ( \case [] -> pure $ Input.LoadI Nothing - [file] -> pure $ Input.LoadI . Just $ file + [file] -> + Input.LoadI . Just + <$> unsupportedStructuredArgument "a file name" file _ -> Left (I.help load) ) @@ -229,7 +681,7 @@ clear = ] ) ( \case - [] -> pure $ Input.ClearI + [] -> pure Input.ClearI _ -> Left (I.help clear) ) @@ -243,7 +695,7 @@ add = ( "`add` adds to the codebase all the definitions from the most recently " <> "typechecked file." ) - $ pure . Input.AddI . Set.fromList . map (Name.unsafeParseText . Text.pack) . unifyArguments + $ bimap P.text (Input.AddI . Set.fromList) . traverse handleNameArg previewAdd :: InputPattern previewAdd = @@ -257,7 +709,7 @@ previewAdd = <> "results. Use `load` to reparse & typecheck the file if the context " <> "has changed." ) - $ pure . Input.PreviewAddI . Set.fromList . map (Name.unsafeParseText . Text.pack) . unifyArguments + $ bimap P.text (Input.PreviewAddI . Set.fromList) . traverse handleNameArg update :: InputPattern update = @@ -272,10 +724,9 @@ update = <> "replacing existing definitions having the same name, and attempts to update all the existing dependents accordingly. If the process" <> "can't be completed automatically, the dependents will be added back to the scratch file" <> "for your review.", - parse = - maybeToEither (I.help update) . \case - [] -> Just Input.Update2I - _ -> Nothing + parse = \case + [] -> pure Input.Update2I + _ -> Left $ I.help update } updateOldNoPatch :: InputPattern @@ -304,12 +755,8 @@ updateOldNoPatch = ) ] ) - ( pure - . Input.UpdateI Input.NoPatch - . Set.fromList - . map (Name.unsafeParseText . Text.pack) - . unifyArguments - ) + $ bimap P.text (Input.UpdateI Input.NoPatch . Set.fromList) + . traverse handleNameArg updateOld :: InputPattern updateOld = @@ -343,13 +790,11 @@ updateOld = ) ] ) - $ unifyArguments `andThen` \case - patchStr : ws -> do - patch <- first P.text $ Path.parseSplit' patchStr - pure $ - Input.UpdateI - (Input.UsePatch patch) - (Set.fromList $ map (Name.unsafeParseText . Text.pack) ws) + \case + patchStr : ws -> first P.text do + patch <- handleSplit'Arg patchStr + Input.UpdateI (Input.UsePatch patch) . Set.fromList + <$> traverse handleNameArg ws [] -> Right $ Input.UpdateI Input.DefaultPatch mempty previewUpdate :: InputPattern @@ -364,7 +809,8 @@ previewUpdate = <> "typechecking results. Use `load` to reparse & typecheck the file if " <> "the context has changed." ) - $ pure . Input.PreviewUpdateI . Set.fromList . map (Name.unsafeParseText . Text.pack) . unifyArguments + $ bimap P.text (Input.PreviewUpdateI . Set.fromList) + . traverse handleNameArg patch :: InputPattern patch = @@ -392,13 +838,16 @@ patch = ] ] ) - $ unifyArguments `andThen` \case - patchStr : ws -> first P.text do - patch <- Path.parseSplit' patchStr - branch <- case ws of - [pathStr] -> Path.parsePath' pathStr - _ -> pure Path.relativeEmpty' - pure $ Input.PropagatePatchI patch branch + $ \case + patchStr : ws -> + Input.PropagatePatchI + <$> first P.text (handleSplit'Arg patchStr) + <*> case ws of + [] -> pure Path.relativeEmpty' + [pathStr] -> first P.text $ handlePath'Arg pathStr + -- __FIXME__: This is a breaking change (previously, too many 3+ would + -- work the same as only one arg) + _ -> Left $ I.help patch [] -> Left $ warn $ @@ -423,12 +872,12 @@ view = <> "not `List.map.doc` (since ? only matches 1 name segment)." ] ) - ( unifyArguments `andThen` \case - (x : xs) -> - (x NE.:| xs) - & traverse parseHashQualifiedName - & fmap (Input.ShowDefinitionI Input.ConsoleLocation Input.ShowDefinitionLocal) - _ -> Left (I.help view) + ( maybe + (Left $ I.help view) + ( fmap (Input.ShowDefinitionI Input.ConsoleLocation Input.ShowDefinitionLocal) + . traverse handleHashQualifiedNameArg + ) + . NE.nonEmpty ) viewGlobal :: InputPattern @@ -443,12 +892,12 @@ viewGlobal = "`view.global` without arguments invokes a search to select definitions to view, which requires that `fzf` can be found within your PATH." ] ) - ( unifyArguments `andThen` \case - (x : xs) -> - (x NE.:| xs) - & traverse parseHashQualifiedName - & fmap (Input.ShowDefinitionI Input.ConsoleLocation Input.ShowDefinitionGlobal) - _ -> Left (I.help viewGlobal) + ( maybe + (Left $ I.help viewGlobal) + ( fmap (Input.ShowDefinitionI Input.ConsoleLocation Input.ShowDefinitionGlobal) + . traverse handleHashQualifiedNameArg + ) + . NE.nonEmpty ) display :: InputPattern @@ -463,12 +912,12 @@ display = "`display` without arguments invokes a search to select a definition to display, which requires that `fzf` can be found within your PATH." ] ) - $ unifyArguments `andThen` \case - (x : xs) -> - (x NE.:| xs) - & traverse parseHashQualifiedName - <&> Input.DisplayI Input.ConsoleLocation - _ -> Left (I.help display) + $ maybe + (Left $ I.help display) + ( fmap (Input.DisplayI Input.ConsoleLocation) + . traverse handleHashQualifiedNameArg + ) + . NE.nonEmpty displayTo :: InputPattern displayTo = @@ -481,11 +930,16 @@ displayTo = makeExample displayTo ["", "foo"] <> "prints a rendered version of the term `foo` to the given file." ) - $ unifyArguments `andThen` \case - file : (x : xs) -> - (x NE.:| xs) - & traverse parseHashQualifiedName - <&> Input.DisplayI (Input.FileLocation file) + $ \case + file : defs -> + maybe + (Left $ I.help displayTo) + ( \defs -> + Input.DisplayI . Input.FileLocation + <$> unsupportedStructuredArgument "a file name" file + <*> traverse handleHashQualifiedNameArg defs + ) + $ NE.nonEmpty defs _ -> Left (I.help displayTo) docs :: InputPattern @@ -500,13 +954,10 @@ docs = "`docs` without arguments invokes a search to select which definition to view documentation for, which requires that `fzf` can be found within your PATH." ] ) - ( unifyArguments `andThen` \case - x : xs -> - (x NE.:| xs) - & traverse Path.parseHQSplit' - & bimap P.text Input.DocsI - _ -> Left (I.help docs) - ) + $ maybe + (Left $ I.help docs) + (bimap P.text Input.DocsI . traverse handleHashQualifiedSplit'Arg) + . NE.nonEmpty api :: InputPattern api = @@ -526,13 +977,10 @@ ui = visibility = I.Visible, args = [("definition to load", Optional, namespaceOrDefinitionArg)], help = P.wrap "`ui` opens the Local UI in the default browser.", - parse = - unifyArguments `andThen` \case - [] -> pure $ Input.UiI Path.relativeEmpty' - [path] -> first P.text $ do - p <- Path.parsePath' path - pure $ Input.UiI p - _ -> Left (I.help ui) + parse = \case + [] -> pure $ Input.UiI Path.relativeEmpty' + [path] -> bimap P.text Input.UiI $ handlePath'Arg path + _ -> Left (I.help ui) } undo :: InputPattern @@ -554,15 +1002,16 @@ viewByPrefix = [("definition to view", OnePlus, definitionQueryArg)] "`view.recursive Foo` prints the definitions of `Foo` and `Foo.blah`." ( fmap (Input.ShowDefinitionByPrefixI Input.ConsoleLocation) - . traverse parseHashQualifiedName - . unifyArguments + . traverse handleHashQualifiedNameArg ) sfind :: InputPattern sfind = - InputPattern "rewrite.find" ["sfind"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg $ parse . unifyArguments + InputPattern "rewrite.find" ["sfind"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg parse where - parse [q] = Input.StructuredFindI (Input.FindLocal Path.empty) <$> parseHashQualifiedName q + parse [q] = + Input.StructuredFindI (Input.FindLocal Path.empty) + <$> handleHashQualifiedNameArg q parse _ = Left "expected exactly one argument" msg = P.lines @@ -591,9 +1040,9 @@ sfind = sfindReplace :: InputPattern sfindReplace = - InputPattern "rewrite" ["sfind.replace"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg $ parse . unifyArguments + InputPattern "rewrite" ["sfind.replace"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg parse where - parse [q] = Input.StructuredFindReplaceI <$> parseHashQualifiedName q + parse [q] = Input.StructuredFindReplaceI <$> handleHashQualifiedNameArg q parse _ = Left "expected exactly one argument" msg :: P.Pretty CT.ColorText msg = @@ -640,10 +1089,17 @@ findIn' cmd mkfscope = I.Visible [("namespace", Required, namespaceArg), ("query", ZeroPlus, exactDefinitionArg)] findHelp - $ unifyArguments `andThen` \case - p : args -> first P.text do - p <- Path.parsePath p - pure (Input.FindI False (mkfscope p) args) + $ \case + p : args -> + Input.FindI False . mkfscope + <$> first P.text (handlePathArg p) + -- __FIXME__: This changes things a bit. Previously, `find` and + -- friends would just expand the numbered args and search + -- for them like any other string, but now it recognizes + -- that you’re trying to look up something you already + -- have, and refuses to. Is that the right thing to do? We + -- _could_ still serialize in this case. + <*> traverse (unsupportedStructuredArgument "text") args _ -> Left findHelp findHelp :: P.Pretty CT.ColorText @@ -690,7 +1146,7 @@ find' cmd fscope = I.Visible [("query", ZeroPlus, exactDefinitionArg)] findHelp - (pure . Input.FindI False fscope . unifyArguments) + (fmap (Input.FindI False fscope) . traverse (unsupportedStructuredArgument "text")) findShallow :: InputPattern findShallow = @@ -705,11 +1161,9 @@ findShallow = ("`list .foo`", "lists the '.foo' namespace.") ] ) - ( unifyArguments `andThen` \case - [] -> pure $ Input.FindShallowI Path.relativeEmpty' - [path] -> first P.text $ do - p <- Path.parsePath' path - pure $ Input.FindShallowI p + ( fmap Input.FindShallowI . \case + [] -> pure Path.relativeEmpty' + [path] -> first P.text $ handlePath'Arg path _ -> Left (I.help findShallow) ) @@ -723,7 +1177,9 @@ findVerbose = ( "`find.verbose` searches for definitions like `find`, but includes hashes " <> "and aliases in the results." ) - (pure . Input.FindI True (Input.FindLocal Path.empty) . unifyArguments) + ( fmap (Input.FindI True $ Input.FindLocal Path.empty) + . traverse (unsupportedStructuredArgument "text") + ) findVerboseAll :: InputPattern findVerboseAll = @@ -735,7 +1191,9 @@ findVerboseAll = ( "`find.all.verbose` searches for definitions like `find.all`, but includes hashes " <> "and aliases in the results." ) - (pure . Input.FindI True (Input.FindLocalAndDeps Path.empty) . unifyArguments) + ( fmap (Input.FindI True $ Input.FindLocalAndDeps Path.empty) + . traverse (unsupportedStructuredArgument "text") + ) findPatch :: InputPattern findPatch = @@ -759,11 +1217,12 @@ renameTerm = ("new location", Required, newNameArg) ] "`move.term foo bar` renames `foo` to `bar`." - ( unifyArguments `andThen` \case - [oldName, newName] -> first P.text do - src <- Path.parseHQSplit' oldName - target <- Path.parseSplit' newName - pure $ Input.MoveTermI src target + ( \case + [oldName, newName] -> + first P.text $ + Input.MoveTermI + <$> handleHashQualifiedSplit'Arg oldName + <*> handleNewName newName _ -> Left . P.warnCallout $ P.wrap @@ -780,11 +1239,12 @@ moveAll = ("new location", Required, newNameArg) ] "`move foo bar` renames the term, type, and namespace foo to bar." - ( unifyArguments `andThen` \case - [oldName, newName] -> first P.text $ do - src <- Path.parsePath' oldName - target <- Path.parsePath' newName - pure $ Input.MoveAllI src target + ( \case + [oldName, newName] -> + first P.text $ + Input.MoveAllI + <$> handlePath'Arg oldName + <*> handleNewPath newName _ -> Left . P.warnCallout $ P.wrap @@ -801,11 +1261,12 @@ renameType = ("new location", Required, newNameArg) ] "`move.type foo bar` renames `foo` to `bar`." - ( unifyArguments `andThen` \case - [oldName, newName] -> first P.text do - src <- Path.parseHQSplit' oldName - target <- Path.parseSplit' newName - pure $ Input.MoveTypeI src target + ( \case + [oldName, newName] -> + first P.text $ + Input.MoveTypeI + <$> handleHashQualifiedSplit'Arg oldName + <*> handleNewName newName _ -> Left . P.warnCallout $ P.wrap @@ -849,11 +1310,11 @@ deleteGen suffix queryCompletionArg target mkTarget = I.Visible [("definition to delete", OnePlus, queryCompletionArg)] info - ( unifyArguments `andThen` \case + ( \case [] -> Left . P.warnCallout $ P.wrap warn - queries -> first P.text do - paths <- traverse Path.parseHQSplit' queries - pure $ Input.DeleteI (mkTarget paths) + queries -> + bimap P.text (Input.DeleteI . mkTarget) $ + traverse handleHashQualifiedSplit'Arg queries ) delete :: InputPattern @@ -897,11 +1358,11 @@ deleteReplacement isTerm = <> str <> " - not the one in place after the edit." ) - ( unifyArguments `andThen` \case - query : patch -> do - patch <- first P.text . traverse Path.parseSplit' $ listToMaybe patch - q <- parseHashQualifiedName query - pure $ input q patch + ( \case + query : patch -> + input + <$> handleHashQualifiedNameArg query + <*> first P.text (traverse handleSplit'Arg $ listToMaybe patch) _ -> Left . P.warnCallout @@ -933,12 +1394,11 @@ deleteProject = P.wrapColumn2 [ ("`delete.project foo`", "deletes the local project `foo`") ], - parse = - unifyArguments `andThen` \case - [name] - | Right project <- tryInto @ProjectName (Text.pack name) -> - Right (Input.DeleteI (DeleteTarget'Project project)) - _ -> Left (showPatternHelp deleteProject) + parse = \case + [name] -> + bimap P.text (Input.DeleteI . DeleteTarget'Project) $ + handleProjectArg name + _ -> Left (showPatternHelp deleteProject) } deleteBranch :: InputPattern @@ -953,13 +1413,11 @@ deleteBranch = [ ("`delete.branch foo/bar`", "deletes the branch `bar` in the project `foo`"), ("`delete.branch /bar`", "deletes the branch `bar` in the current project") ], - parse = - unifyArguments `andThen` \case - [name] -> - case tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) (Text.pack name) of - Left _ -> Left (showPatternHelp deleteBranch) - Right projectAndBranch -> Right (Input.DeleteI (DeleteTarget'ProjectBranch projectAndBranch)) - _ -> Left (showPatternHelp deleteBranch) + parse = \case + [name] -> + bimap P.text (Input.DeleteI . DeleteTarget'ProjectBranch) $ + handleProjectAndBranchArg name + _ -> Left (showPatternHelp deleteBranch) } where suggestionsConfig = @@ -983,11 +1441,12 @@ aliasTerm = I.Visible [("term to alias", Required, exactDefinitionTermQueryArg), ("alias name", Required, newNameArg)] "`alias.term foo bar` introduces `bar` with the same definition as `foo`." - $ unifyArguments `andThen` \case - [oldName, newName] -> first P.text do - source <- Path.parseShortHashOrHQSplit' oldName - target <- Path.parseSplit' newName - pure $ Input.AliasTermI source target + $ \case + [oldName, newName] -> + first P.text $ + Input.AliasTermI + <$> handleShortHashOrHQSplit'Arg oldName + <*> handleSplit'Arg newName _ -> Left . warn $ P.wrap @@ -1001,11 +1460,12 @@ aliasType = I.Visible [("type to alias", Required, exactDefinitionTypeQueryArg), ("alias name", Required, newNameArg)] "`alias.type Foo Bar` introduces `Bar` with the same definition as `Foo`." - $ unifyArguments `andThen` \case - [oldName, newName] -> first P.text do - source <- Path.parseShortHashOrHQSplit' oldName - target <- Path.parseSplit' newName - pure $ Input.AliasTypeI source target + $ \case + [oldName, newName] -> + first P.text $ + Input.AliasTypeI + <$> handleShortHashOrHQSplit'Arg oldName + <*> handleSplit'Arg newName _ -> Left . warn $ P.wrap @@ -1027,11 +1487,12 @@ aliasMany = <> "creates aliases `.quux.foo.foo` and `.quux.bar.bar`." ] ) - $ unifyArguments `andThen` \case - srcs@(_ : _) Cons.:> dest -> first P.text do - sourceDefinitions <- traverse Path.parseHQSplit srcs - destNamespace <- Path.parsePath' dest - pure $ Input.AliasManyI sourceDefinitions destNamespace + $ \case + srcs@(_ : _) Cons.:> dest -> + first P.text $ + Input.AliasManyI + <$> traverse handleHashQualifiedSplitArg srcs + <*> handlePath'Arg dest _ -> Left (I.help aliasMany) up :: InputPattern @@ -1073,11 +1534,9 @@ cd = ] ] ) - $ unifyArguments `andThen` \case - [".."] -> Right Input.UpI - [p] -> first P.text do - p <- Path.parsePath' p - pure . Input.SwitchBranchI $ p + $ \case + [Left ".."] -> Right Input.UpI + [p] -> bimap P.text Input.SwitchBranchI $ handlePath'Arg p _ -> Left (I.help cd) back :: InputPattern @@ -1105,7 +1564,7 @@ deleteNamespace = I.Visible [("namespace to delete", Required, namespaceArg)] "`delete.namespace ` deletes the namespace `foo`" - (deleteNamespaceParser (I.help deleteNamespace) Input.Try . unifyArguments) + (deleteNamespaceParser (I.help deleteNamespace) Input.Try) deleteNamespaceForce :: InputPattern deleteNamespaceForce = @@ -1117,17 +1576,17 @@ deleteNamespaceForce = ( "`delete.namespace.force ` deletes the namespace `foo`," <> "deletion will proceed even if other code depends on definitions in foo." ) - (deleteNamespaceParser (I.help deleteNamespaceForce) Input.Force . unifyArguments) + (deleteNamespaceParser (I.help deleteNamespaceForce) Input.Force) -deleteNamespaceParser :: P.Pretty CT.ColorText -> Input.Insistence -> [String] -> Either (P.Pretty CT.ColorText) Input +deleteNamespaceParser :: P.Pretty CT.ColorText -> Input.Insistence -> I.Arguments -> Either (P.Pretty CT.ColorText) Input deleteNamespaceParser helpText insistence = \case - ["."] -> + [Left "."] -> first fromString . pure $ Input.DeleteI (DeleteTarget'Namespace insistence Nothing) - [p] -> first P.text do - p <- Path.parseSplit' p - pure $ Input.DeleteI (DeleteTarget'Namespace insistence (Just p)) + [p] -> + bimap P.text (Input.DeleteI . DeleteTarget'Namespace insistence . pure) $ + handleSplit'Arg p _ -> Left helpText deletePatch :: InputPattern @@ -1138,23 +1597,20 @@ deletePatch = I.Visible [("patch to delete", Required, patchArg)] "`delete.patch ` deletes the patch `foo`" - $ unifyArguments `andThen` \case - [p] -> first P.text do - p <- Path.parseSplit' p - pure . Input.DeleteI $ DeleteTarget'Patch p + $ \case + [p] -> + bimap P.text (Input.DeleteI . DeleteTarget'Patch) $ handleSplit'Arg p _ -> Left (I.help deletePatch) -movePatch :: String -> String -> Either (P.Pretty CT.ColorText) Input -movePatch src dest = first P.text do - src <- Path.parseSplit' src - dest <- Path.parseSplit' dest - pure $ Input.MovePatchI src dest +movePatch :: I.Argument -> I.Argument -> Either (P.Pretty CT.ColorText) Input +movePatch src dest = + first P.text $ + Input.MovePatchI <$> handleSplit'Arg src <*> handleSplit'Arg dest -copyPatch' :: String -> String -> Either (P.Pretty CT.ColorText) Input -copyPatch' src dest = first P.text do - src <- Path.parseSplit' src - dest <- Path.parseSplit' dest - pure $ Input.CopyPatchI src dest +copyPatch' :: I.Argument -> I.Argument -> Either (P.Pretty CT.ColorText) Input +copyPatch' src dest = + first P.text $ + Input.CopyPatchI <$> handleSplit'Arg src <*> handleSplit'Arg dest copyPatch :: InputPattern copyPatch = @@ -1164,7 +1620,7 @@ copyPatch = I.Visible [("patch to copy", Required, patchArg), ("copy destination", Required, newNameArg)] "`copy.patch foo bar` copies the patch `foo` to `bar`." - $ unifyArguments `andThen` \case + $ \case [src, dest] -> copyPatch' src dest _ -> Left (I.help copyPatch) @@ -1176,7 +1632,7 @@ renamePatch = I.Visible [("patch", Required, patchArg), ("new location", Required, newNameArg)] "`move.patch foo bar` renames the patch `foo` to `bar`." - $ unifyArguments `andThen` \case + $ \case [src, dest] -> movePatch src dest _ -> Left (I.help renamePatch) @@ -1188,11 +1644,10 @@ renameBranch = I.Visible [("namespace to move", Required, namespaceArg), ("new location", Required, newNameArg)] "`move.namespace foo bar` renames the path `foo` to `bar`." - $ unifyArguments `andThen` \case - [src, dest] -> first P.text do - src <- Path.parsePath' src - dest <- Path.parsePath' dest - pure $ Input.MoveBranchI src dest + $ \case + [src, dest] -> + first P.text $ + Input.MoveBranchI <$> handlePath'Arg src <*> handlePath'Arg dest _ -> Left (I.help renameBranch) history :: InputPattern @@ -1211,10 +1666,10 @@ history = ) ] ) - $ unifyArguments `andThen` \case - [src] -> first P.text do - p <- Input.parseBranchId src - pure $ Input.HistoryI (Just 10) (Just 10) p + $ \case + [src] -> + bimap P.text (Input.HistoryI (Just 10) (Just 10)) $ + handleBranchIdArg src [] -> pure $ Input.HistoryI (Just 10) (Just 10) (Right Path.currentPath) _ -> Left (I.help history) @@ -1239,11 +1694,11 @@ forkLocal = ) ] ) - $ unifyArguments `andThen` \case - [src, dest] -> do - src <- Input.parseBranchId2 src - dest <- parseBranchRelativePath dest - pure $ Input.ForkLocalBranchI src dest + $ \case + [src, dest] -> + Input.ForkLocalBranchI + <$> handleBranchId2Arg src + <*> handleBranchRelativePathArg dest _ -> Left (I.help forkLocal) reset :: InputPattern @@ -1262,35 +1717,18 @@ reset = ("`reset #pvfd222s8n /topic`", "reset the branch `topic` of the current project to the causal `#pvfd222s8n`.") ] ) - ( maybeToEither (I.help reset) - . ( \case - arg0 : restArgs -> do - arg0 <- branchIdOrProject arg0 - arg1 <- case restArgs of - [] -> pure Nothing - arg1 : [] -> Just <$> parseLooseCodeOrProject arg1 - _ -> Nothing - Just (Input.ResetI arg0 arg1) - _ -> Nothing - ) - . unifyArguments + ( \case + [arg0] -> + Input.ResetI + <$> first P.text (handleBranchIdOrProjectArg arg0) + <*> pure Nothing + [arg0, arg1] -> + Input.ResetI + <$> first P.text (handleBranchIdOrProjectArg arg0) + <*> bimap P.text pure (handleLooseCodeOrProjectArg arg1) + _ -> Left $ I.help reset ) where - branchIdOrProject :: - String -> - Maybe - ( These - Input.BranchId - (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) - ) - branchIdOrProject str = - let branchIdRes = Input.parseBranchId str - projectRes = tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) (Text.pack str) - in case (branchIdRes, projectRes) of - (Left _, Left _) -> Nothing - (Left _, Right pr) -> Just (That pr) - (Right bid, Left _) -> Just (This bid) - (Right bid, Right pr) -> Just (These bid pr) config = ProjectBranchSuggestionsConfig { showProjectCompletions = False, @@ -1319,10 +1757,8 @@ resetRoot = ] ] ) - $ unifyArguments `andThen` \case - [src] -> first P.text $ do - src <- Input.parseBranchId src - pure $ Input.ResetRootI src + $ \case + [src] -> bimap P.text Input.ResetRootI $ handleBranchIdArg src _ -> Left (I.help resetRoot) pull :: InputPattern @@ -1387,24 +1823,27 @@ pullImpl name aliases verbosity pullMode addendum = do explainRemote Pull ], parse = - maybeToEither (I.help self) + fmap + ( \sourceTarget -> + Input.PullRemoteBranchI + sourceTarget + SyncMode.ShortCircuit + pullMode + verbosity + ) . ( \case - [] -> Just $ Input.PullRemoteBranchI Input.PullSourceTarget0 SyncMode.ShortCircuit pullMode verbosity - [sourceString] -> do - source <- parsePullSource (Text.pack sourceString) - Just $ Input.PullRemoteBranchI (Input.PullSourceTarget1 source) SyncMode.ShortCircuit pullMode verbosity - [sourceString, targetString] -> do - source <- parsePullSource (Text.pack sourceString) - target <- parseLooseCodeOrProject targetString - Just $ - Input.PullRemoteBranchI - (Input.PullSourceTarget2 source target) - SyncMode.ShortCircuit - pullMode - verbosity - _ -> Nothing + [] -> pure $ Input.PullSourceTarget0 + [sourceString] -> + first P.text $ + Input.PullSourceTarget1 + <$> handlePullSourceArg sourceString + [sourceString, targetString] -> + first P.text $ + Input.PullSourceTarget2 + <$> handlePullSourceArg sourceString + <*> handleLooseCodeOrProjectArg targetString + _ -> Left $ I.help self ) - . unifyArguments } pullExhaustive :: InputPattern @@ -1425,35 +1864,27 @@ pullExhaustive = <> "versions M1l and earlier. It may be extra slow!" ] ) - ( maybeToEither (I.help pullExhaustive) + ( fmap + ( \sourceTarget -> + Input.PullRemoteBranchI + sourceTarget + SyncMode.Complete + Input.PullWithHistory + Verbosity.Verbose + ) . ( \case - [] -> - Just $ - Input.PullRemoteBranchI - Input.PullSourceTarget0 - SyncMode.Complete - Input.PullWithHistory - Verbosity.Verbose - [sourceString] -> do - source <- parsePullSource (Text.pack sourceString) - Just $ - Input.PullRemoteBranchI - (Input.PullSourceTarget1 source) - SyncMode.Complete - Input.PullWithHistory - Verbosity.Verbose - [sourceString, targetString] -> do - source <- parsePullSource (Text.pack sourceString) - target <- parseLooseCodeOrProject targetString - Just $ - Input.PullRemoteBranchI - (Input.PullSourceTarget2 source target) - SyncMode.Complete - Input.PullWithHistory - Verbosity.Verbose - _ -> Nothing + [] -> pure $ Input.PullSourceTarget0 + [sourceString] -> + first P.text $ + Input.PullSourceTarget1 + <$> handlePullSourceArg sourceString + [sourceString, targetString] -> + first P.text $ + Input.PullSourceTarget2 + <$> handlePullSourceArg sourceString + <*> handleLooseCodeOrProjectArg targetString + _ -> Left $ I.help pullExhaustive ) - . unifyArguments ) debugTabCompletion :: InputPattern @@ -1468,7 +1899,7 @@ debugTabCompletion = P.wrap $ "Completions which are finished are prefixed with a * represent finished completions." ] ) - (Right . Input.DebugTabCompletionI . unifyArguments) + (fmap Input.DebugTabCompletionI . traverse (unsupportedStructuredArgument "text")) debugFuzzyOptions :: InputPattern debugFuzzyOptions = @@ -1485,9 +1916,11 @@ debugFuzzyOptions = P.wrap $ "or `debug.fuzzy-options merge - _`" ] ) - $ unifyArguments `andThen` \case + $ \case (cmd : args) -> - Right $ Input.DebugFuzzyOptionsI cmd args + Input.DebugFuzzyOptionsI + <$> unsupportedStructuredArgument "a command" cmd + <*> traverse (unsupportedStructuredArgument "text") args _ -> Left (I.help debugFuzzyOptions) debugFormat :: InputPattern @@ -1538,25 +1971,25 @@ push = explainRemote Push ] ) - $ unifyArguments `andThen` \args -> do - sourceTarget <- - case args of - [] -> Right Input.PushSourceTarget0 - [targetStr] -> do - target <- parsePushTarget targetStr - Right (Input.PushSourceTarget1 target) - [targetStr, sourceStr] -> do - target <- parsePushTarget targetStr - source <- parsePushSource sourceStr - Right (Input.PushSourceTarget2 source target) - _ -> Left (I.help push) - Right $ - Input.PushRemoteBranchI - Input.PushRemoteBranchInput - { sourceTarget, - pushBehavior = PushBehavior.RequireNonEmpty, - syncMode = SyncMode.ShortCircuit - } + $ fmap + ( \sourceTarget -> + Input.PushRemoteBranchI + Input.PushRemoteBranchInput + { sourceTarget, + pushBehavior = PushBehavior.RequireNonEmpty, + syncMode = SyncMode.ShortCircuit + } + ) + . \case + [] -> pure Input.PushSourceTarget0 + [targetStr] -> + bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr, sourceStr] -> + first P.text $ + Input.PushSourceTarget2 + <$> handlePushSourceArg sourceStr + <*> handlePushTargetArg targetStr + _ -> Left (I.help push) where suggestionsConfig = ProjectBranchSuggestionsConfig @@ -1594,25 +2027,25 @@ pushCreate = explainRemote Push ] ) - $ unifyArguments `andThen` \args -> do - sourceTarget <- - case args of - [] -> Right Input.PushSourceTarget0 - [targetStr] -> do - target <- parsePushTarget targetStr - Right (Input.PushSourceTarget1 target) - [targetStr, sourceStr] -> do - target <- parsePushTarget targetStr - source <- parsePushSource sourceStr - Right (Input.PushSourceTarget2 source target) - _ -> Left (I.help pushForce) - Right $ - Input.PushRemoteBranchI - Input.PushRemoteBranchInput - { sourceTarget, - pushBehavior = PushBehavior.RequireEmpty, - syncMode = SyncMode.ShortCircuit - } + $ fmap + ( \sourceTarget -> + Input.PushRemoteBranchI + Input.PushRemoteBranchInput + { sourceTarget, + pushBehavior = PushBehavior.RequireEmpty, + syncMode = SyncMode.ShortCircuit + } + ) + . \case + [] -> pure Input.PushSourceTarget0 + [targetStr] -> + bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr, sourceStr] -> + first P.text $ + Input.PushSourceTarget2 + <$> handlePushSourceArg sourceStr + <*> handlePushTargetArg targetStr + _ -> Left (I.help pushForce) where suggestionsConfig = ProjectBranchSuggestionsConfig @@ -1629,25 +2062,25 @@ pushForce = I.Hidden [("remote destination", Optional, remoteNamespaceArg), ("local source", Optional, namespaceOrProjectBranchArg suggestionsConfig)] (P.wrap "Like `push`, but overwrites any remote namespace.") - $ unifyArguments `andThen` \args -> do - sourceTarget <- - case args of - [] -> Right Input.PushSourceTarget0 - [targetStr] -> do - target <- parsePushTarget targetStr - Right (Input.PushSourceTarget1 target) - [targetStr, sourceStr] -> do - target <- parsePushTarget targetStr - source <- parsePushSource sourceStr - Right (Input.PushSourceTarget2 source target) - _ -> Left (I.help pushForce) - Right $ - Input.PushRemoteBranchI - Input.PushRemoteBranchInput - { sourceTarget, - pushBehavior = PushBehavior.ForcePush, - syncMode = SyncMode.ShortCircuit - } + $ fmap + ( \sourceTarget -> + Input.PushRemoteBranchI + Input.PushRemoteBranchInput + { sourceTarget, + pushBehavior = PushBehavior.ForcePush, + syncMode = SyncMode.ShortCircuit + } + ) + . \case + [] -> pure Input.PushSourceTarget0 + [targetStr] -> + bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr, sourceStr] -> + first P.text $ + Input.PushSourceTarget2 + <$> handlePushSourceArg sourceStr + <*> handlePushTargetArg targetStr + _ -> Left (I.help pushForce) where suggestionsConfig = ProjectBranchSuggestionsConfig @@ -1674,25 +2107,25 @@ pushExhaustive = <> "versions M1l and earlier. It may be extra slow!" ] ) - $ unifyArguments `andThen` \args -> do - sourceTarget <- - case args of - [] -> Right Input.PushSourceTarget0 - [targetStr] -> do - target <- parsePushTarget targetStr - Right (Input.PushSourceTarget1 target) - [targetStr, sourceStr] -> do - target <- parsePushTarget targetStr - source <- parsePushSource sourceStr - Right (Input.PushSourceTarget2 source target) - _ -> Left (I.help pushExhaustive) - Right $ - Input.PushRemoteBranchI - Input.PushRemoteBranchInput - { sourceTarget, - pushBehavior = PushBehavior.RequireNonEmpty, - syncMode = SyncMode.Complete - } + $ fmap + ( \sourceTarget -> + Input.PushRemoteBranchI + Input.PushRemoteBranchInput + { sourceTarget, + pushBehavior = PushBehavior.RequireNonEmpty, + syncMode = SyncMode.Complete + } + ) + . \case + [] -> pure Input.PushSourceTarget0 + [targetStr] -> + bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr, sourceStr] -> + first P.text $ + Input.PushSourceTarget2 + <$> handlePushSourceArg sourceStr + <*> handlePushTargetArg targetStr + _ -> Left (I.help pushExhaustive) where suggestionsConfig = ProjectBranchSuggestionsConfig @@ -1715,16 +2148,14 @@ squashMerge = <> "discarding the history of `src` in the process." <> "The resulting `dest` will have (at most) 1" <> "additional history entry.", - parse = - maybeToEither (I.help squashMerge) - . ( \case - [src, dest] -> do - src <- parseLooseCodeOrProject src - dest <- parseLooseCodeOrProject dest - Just $ Input.MergeLocalBranchI src dest Branch.SquashMerge - _ -> Nothing - ) - . unifyArguments + parse = \case + [src, dest] -> + first P.text $ + Input.MergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> handleLooseCodeOrProjectArg dest + <*> pure Branch.SquashMerge + _ -> Left $ I.help squashMerge } where suggestionsConfig = @@ -1764,18 +2195,20 @@ mergeLocal = ) ] ) - ( maybeToEither (I.help mergeLocal) - . ( \case - [src] -> do - src <- parseLooseCodeOrProject src - Just $ Input.MergeLocalBranchI src (This Path.relativeEmpty') Branch.RegularMerge - [src, dest] -> do - src <- parseLooseCodeOrProject src - dest <- parseLooseCodeOrProject dest - Just $ Input.MergeLocalBranchI src dest Branch.RegularMerge - _ -> Nothing - ) - . unifyArguments + ( \case + [src] -> + first P.text $ + Input.MergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> pure (This Path.relativeEmpty') + <*> pure Branch.RegularMerge + [src, dest] -> + first P.text $ + Input.MergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> handleLooseCodeOrProjectArg dest + <*> pure Branch.RegularMerge + _ -> Left $ I.help mergeLocal ) where config = @@ -1814,14 +2247,17 @@ diffNamespace = ) ] ) - ( unifyArguments `andThen` \case - [before, after] -> first P.text do - before <- Input.parseBranchId before - after <- Input.parseBranchId after - pure $ Input.DiffNamespaceI before after - [before] -> first P.text do - before <- Input.parseBranchId before - pure $ Input.DiffNamespaceI before (Right Path.currentPath) + ( \case + [before, after] -> + first P.text $ + Input.DiffNamespaceI + <$> handleBranchIdArg before + <*> handleBranchIdArg after + [before] -> + first P.text $ + Input.DiffNamespaceI + <$> handleBranchIdArg before + <*> pure (pure Path.currentPath) _ -> Left $ I.help diffNamespace ) where @@ -1848,18 +2284,18 @@ previewMergeLocal = ) ] ) - ( maybeToEither (I.help previewMergeLocal) - . ( \case - [src] -> do - src <- parseLooseCodeOrProject src - pure $ Input.PreviewMergeLocalBranchI src (This Path.relativeEmpty') - [src, dest] -> do - src <- parseLooseCodeOrProject src - dest <- parseLooseCodeOrProject dest - pure $ Input.PreviewMergeLocalBranchI src dest - _ -> Nothing - ) - . unifyArguments + ( \case + [src] -> + first P.text $ + Input.PreviewMergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> pure (This Path.relativeEmpty') + [src, dest] -> + first P.text $ + Input.PreviewMergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> handleLooseCodeOrProjectArg dest + _ -> Left $ I.help previewMergeLocal ) where suggestionsConfig = @@ -1896,12 +2332,12 @@ replaceEdit f = self ) ] ) - ( unifyArguments `andThen` \case - source : target : patch -> do - patch <- first P.text <$> traverse Path.parseSplit' $ listToMaybe patch - sourcehq <- parseHashQualifiedName source - targethq <- parseHashQualifiedName target - pure $ f sourcehq targethq patch + ( \case + source : target : patch -> + f + <$> handleHashQualifiedNameArg source + <*> handleHashQualifiedNameArg target + <*> first P.text (traverse handleSplit'Arg $ listToMaybe patch) _ -> Left $ I.help self ) @@ -1937,12 +2373,12 @@ edit = "`edit` without arguments invokes a search to select a definition for editing, which requires that `fzf` can be found within your PATH." ], parse = - unifyArguments `andThen` \case - (x : xs) -> - (x NE.:| xs) - & traverse parseHashQualifiedName - <&> (Input.ShowDefinitionI Input.LatestFileLocation Input.ShowDefinitionLocal) - [] -> Left (I.help edit) + maybe + (Left $ I.help edit) + ( fmap (Input.ShowDefinitionI Input.LatestFileLocation Input.ShowDefinitionLocal) + . traverse handleHashQualifiedNameArg + ) + . NE.nonEmpty } editNamespace :: InputPattern @@ -1957,7 +2393,7 @@ editNamespace = [ "`edit.namespace` will load all terms and types contained within the current namespace into your scratch file. This includes definitions in namespaces, but excludes libraries.", "`edit.namespace ns1 ns2 ...` loads the terms and types contained within the provided namespaces." ], - parse = Right . Input.EditNamespaceI . fmap (Path.unsafeParseText . Text.pack) . unifyArguments + parse = bimap P.text Input.EditNamespaceI . traverse handlePathArg } topicNameArg :: ArgumentType @@ -1965,7 +2401,7 @@ topicNameArg = let topics = Map.keys helpTopicsMap in ArgumentType { typeName = "topic", - suggestions = \q _ _ _ -> pure (exactComplete q $ topics), + suggestions = \q _ _ _ -> pure (exactComplete q topics), fzfResolver = Just $ Resolvers.fuzzySelectFromList (Text.pack <$> topics) } @@ -1985,11 +2421,13 @@ helpTopics = I.Visible [("topic", Optional, topicNameArg)] ("`help-topics` lists all topics and `help-topics ` shows an explanation of that topic.") - ( unifyArguments `andThen` \case + ( \case [] -> Left topics - [topic] -> case Map.lookup topic helpTopicsMap of - Nothing -> Left . warn $ "I don't know of that topic. Try `help-topics`." - Just t -> Left t + [topic] -> do + topic <- unsupportedStructuredArgument "a help topic" topic + case Map.lookup topic helpTopicsMap of + Nothing -> Left . warn $ "I don't know of that topic. Try `help-topics`." + Just t -> Left t _ -> Left $ warn "Use `help-topics ` or `help-topics`." ) where @@ -2168,14 +2606,15 @@ help = I.Visible [("command", Optional, commandNameArg)] "`help` shows general help and `help ` shows help for one command." - $ unifyArguments `andThen` \case + $ \case [] -> Left $ intercalateMap "\n\n" showPatternHelp visibleInputs - [cmd] -> + [cmd] -> do + cmd <- unsupportedStructuredArgument "a command" cmd case (Map.lookup cmd commandsByName, isHelp cmd) of (Nothing, Just msg) -> Left msg (Nothing, Nothing) -> Left . warn $ "I don't know of that command. Try `help`." @@ -2230,11 +2669,10 @@ viewPatch = ) ] ) - $ unifyArguments `andThen` \case + $ \case [] -> Right $ Input.ListEditsI Nothing - [patchStr] -> mapLeft P.text do - patch <- Path.parseSplit' patchStr - Right $ Input.ListEditsI (Just patch) + [patchStr] -> + bimap P.text (Input.ListEditsI . pure) $ handleSplit'Arg patchStr _ -> Left $ warn "`view.patch` takes a patch and that's it." names :: Input.IsGlobal -> InputPattern @@ -2245,13 +2683,8 @@ names isGlobal = I.Visible [("name or hash", Required, definitionQueryArg)] (P.wrap $ makeExample (names isGlobal) ["foo"] <> " shows the hash and all known names for `foo`.") - $ unifyArguments `andThen` \case - [thing] -> case HQ.parseText (Text.pack thing) of - Just hq -> Right $ Input.NamesI isGlobal hq - Nothing -> - Left $ - "I was looking for one of these forms: " - <> P.blue "foo .foo.bar foo#abc #abcde .foo.bar#asdf" + $ \case + [thing] -> Input.NamesI isGlobal <$> handleHashQualifiedNameArg thing _ -> Left (I.help (names isGlobal)) where cmdName = if isGlobal then "names.global" else "names" @@ -2264,8 +2697,8 @@ dependents = I.Visible [("definition", Required, definitionQueryArg)] "List the named dependents of the specified definition." - $ unifyArguments `andThen` \case - [thing] -> fmap Input.ListDependentsI $ parseHashQualifiedName thing + $ \case + [thing] -> Input.ListDependentsI <$> handleHashQualifiedNameArg thing _ -> Left (I.help dependents) dependencies = InputPattern @@ -2274,8 +2707,8 @@ dependencies = I.Visible [("definition", Required, definitionQueryArg)] "List the dependencies of the specified definition." - $ unifyArguments `andThen` \case - [thing] -> fmap Input.ListDependenciesI $ parseHashQualifiedName thing + $ \case + [thing] -> Input.ListDependenciesI <$> handleHashQualifiedNameArg thing _ -> Left (I.help dependencies) namespaceDependencies :: InputPattern @@ -2286,10 +2719,9 @@ namespaceDependencies = I.Visible [("namespace", Optional, namespaceArg)] "List the external dependencies of the specified namespace." - $ unifyArguments `andThen` \case - [p] -> first P.text do - p <- Path.parsePath' p - pure $ Input.NamespaceDependenciesI (Just p) + $ \case + [p] -> + bimap P.text (Input.NamespaceDependenciesI . pure) $ handlePath'Arg p [] -> pure (Input.NamespaceDependenciesI Nothing) _ -> Left (I.help namespaceDependencies) @@ -2341,8 +2773,8 @@ debugTerm = I.Hidden [("term", Required, exactDefinitionTermQueryArg)] "View debugging information for a given term." - ( unifyArguments `andThen` \case - [thing] -> fmap (Input.DebugTermI False) $ parseHashQualifiedName thing + ( \case + [thing] -> Input.DebugTermI False <$> handleHashQualifiedNameArg thing _ -> Left (I.help debugTerm) ) @@ -2354,8 +2786,8 @@ debugTermVerbose = I.Hidden [("term", Required, exactDefinitionTermQueryArg)] "View verbose debugging information for a given term." - ( unifyArguments `andThen` \case - [thing] -> fmap (Input.DebugTermI True) $ parseHashQualifiedName thing + ( \case + [thing] -> Input.DebugTermI True <$> handleHashQualifiedNameArg thing _ -> Left (I.help debugTermVerbose) ) @@ -2367,8 +2799,8 @@ debugType = I.Hidden [("type", Required, exactDefinitionTypeQueryArg)] "View debugging information for a given type." - ( unifyArguments `andThen` \case - [thing] -> fmap (Input.DebugTypeI) $ parseHashQualifiedName thing + ( \case + [thing] -> Input.DebugTypeI <$> handleHashQualifiedNameArg thing _ -> Left (I.help debugType) ) @@ -2415,11 +2847,12 @@ debugNameDiff = args = [("before namespace", Required, namespaceArg), ("after namespace", Required, namespaceArg)], help = P.wrap "List all name changes between two causal hashes. Does not detect patch changes.", parse = - ( unifyArguments `andThen` \case - [from, to] -> first fromString $ do - fromSCH <- Input.parseShortCausalHash from - toSCH <- Input.parseShortCausalHash to - pure $ Input.DebugNameDiffI fromSCH toSCH + ( \case + [from, to] -> + first P.text $ + Input.DebugNameDiffI + <$> handleShortCausalHashArg from + <*> handleShortCausalHashArg to _ -> Left (I.help debugNameDiff) ) } @@ -2474,10 +2907,11 @@ docsToHtml = ) ] ) - $ unifyArguments `andThen` \case - [namespacePath, destinationFilePath] -> first P.text do - np <- Path.parsePath' namespacePath - pure $ Input.DocsToHtmlI np destinationFilePath + $ \case + [namespacePath, destinationFilePath] -> + Input.DocsToHtmlI + <$> first P.text (handlePath'Arg namespacePath) + <*> unsupportedStructuredArgument "a file name" destinationFilePath _ -> Left $ showPatternHelp docsToHtml docToMarkdown :: InputPattern @@ -2493,10 +2927,9 @@ docToMarkdown = ) ] ) - $ unifyArguments `andThen` \case - [docNameText] -> first fromString $ do - docName <- maybeToEither "Invalid name" . Name.parseText . Text.pack $ docNameText - pure $ Input.DocToMarkdownI docName + $ \case + [docNameText] -> + bimap P.text Input.DocToMarkdownI $ handleNameArg docNameText _ -> Left $ showPatternHelp docToMarkdown execute :: InputPattern @@ -2515,9 +2948,10 @@ execute = ) ] ) - $ unifyArguments `andThen` \case - [w] -> pure $ Input.ExecuteI (Text.pack w) [] - w : ws -> pure $ Input.ExecuteI (Text.pack w) ws + $ \case + main : args -> + Input.ExecuteI (Text.pack $ unifyArgument main) + <$> traverse (unsupportedStructuredArgument "a command-line argument") args _ -> Left $ showPatternHelp execute saveExecuteResult :: InputPattern @@ -2530,8 +2964,8 @@ saveExecuteResult = ( "`add.run name` adds to the codebase the result of the most recent `run` command" <> "as `name`." ) - $ unifyArguments `andThen` \case - [w] -> pure $ Input.SaveExecuteResultI (Name.unsafeParseText (Text.pack w)) + $ \case + [w] -> first P.text $ Input.SaveExecuteResultI <$> handleNameArg w _ -> Left $ showPatternHelp saveExecuteResult ioTest :: InputPattern @@ -2547,10 +2981,9 @@ ioTest = "Runs `!mytest`, where `mytest` is a delayed test that can use the `IO` and `Exception` abilities." ) ], - parse = - unifyArguments `andThen` \case - [thing] -> fmap Input.IOTestI $ parseHashQualifiedName thing - _ -> Left $ showPatternHelp ioTest + parse = \case + [thing] -> Input.IOTestI <$> handleHashQualifiedNameArg thing + _ -> Left $ showPatternHelp ioTest } ioTestAll :: InputPattern @@ -2586,9 +3019,11 @@ makeStandalone = ) ] ) - $ unifyArguments `andThen` \case + $ \case [main, file] -> - Input.MakeStandaloneI file <$> parseHashQualifiedName main + Input.MakeStandaloneI + <$> unsupportedStructuredArgument "a file name" file + <*> handleHashQualifiedNameArg main _ -> Left $ showPatternHelp makeStandalone runScheme :: InputPattern @@ -2604,8 +3039,10 @@ runScheme = ) ] ) - $ unifyArguments `andThen` \case - main : args -> Right $ Input.ExecuteSchemeI (Text.pack main) args + $ \case + main : args -> + Input.ExecuteSchemeI (Text.pack $ unifyArgument main) + <$> traverse (unsupportedStructuredArgument "a command-line argument") args _ -> Left $ showPatternHelp runScheme compileScheme :: InputPattern @@ -2623,9 +3060,11 @@ compileScheme = ) ] ) - $ unifyArguments `andThen` \case + $ \case [main, file] -> - Input.CompileSchemeI (Text.pack file) <$> parseHashQualifiedName main + Input.CompileSchemeI . Text.pack + <$> unsupportedStructuredArgument "a file name" file + <*> handleHashQualifiedNameArg main _ -> Left $ showPatternHelp compileScheme createAuthor :: InputPattern @@ -2646,19 +3085,21 @@ createAuthor = <> backtick (P.group ("metadata.copyrightHolders" <> ".")) ) ) - ( unifyArguments `andThen` \case - symbolStr : authorStr@(_ : _) -> first P.text do - symbol <- - Megaparsec.runParser (Megaparsec.withParsecT (fmap NameSegment.renderParseErr) NameSegment.segmentP <* Megaparsec.eof) "" symbolStr - & mapLeft (Text.pack . Megaparsec.errorBundlePretty) - -- let's have a real parser in not too long - let author :: Text - author = Text.pack $ case (unwords authorStr) of - quoted@('"' : _) -> (init . tail) quoted - bare -> bare - pure $ Input.CreateAuthorI symbol author - _ -> Left $ showPatternHelp createAuthor - ) + \case + symbolStr : authorStr@(_ : _) -> + Input.CreateAuthorI + <$> first P.text (handleRelativeNameSegmentArg symbolStr) + <*> fmap + (parseAuthorName . unwords) + (traverse (unsupportedStructuredArgument "text") authorStr) + _ -> Left $ showPatternHelp createAuthor + where + -- let's have a real parser in not too long + parseAuthorName :: String -> Text + parseAuthorName = + Text.pack . \case + ('"' : quoted) -> init quoted + bare -> bare gist :: InputPattern gist = @@ -2681,10 +3122,11 @@ gist = <> "`push.create` of the current namespace to your Unison Share codebase wherever you like!" ] ) - ( unifyArguments `andThen` \case - [repoString] -> do - repo <- parseWriteGitRepo "gist git repo" repoString - pure (Input.GistI (Input.GistInput repo)) + ( \case + [repoString] -> + fmap (Input.GistI . Input.GistInput) + . parseWriteGitRepo "gist git repo" + =<< unsupportedStructuredArgument "a VCS repository" repoString _ -> Left (showPatternHelp gist) ) @@ -2728,15 +3170,14 @@ diffNamespaceToPatch = visibility = I.Visible, args = [], help = P.wrap "Create a patch from a namespace diff.", - parse = - unifyArguments `andThen` \case - [branchId1, branchId2, patch] -> - mapLeft P.text do - branchId1 <- Input.parseBranchId branchId1 - branchId2 <- Input.parseBranchId branchId2 - patch <- Path.parseSplit' patch - pure (Input.DiffNamespaceToPatchI Input.DiffNamespaceToPatchInput {branchId1, branchId2, patch}) - _ -> Left (showPatternHelp diffNamespaceToPatch) + parse = \case + [branchId1, branchId2, patch] -> + bimap P.text Input.DiffNamespaceToPatchI $ + Input.DiffNamespaceToPatchInput + <$> handleBranchIdArg branchId1 + <*> handleBranchIdArg branchId2 + <*> handleSplit'Arg patch + _ -> Left (showPatternHelp diffNamespaceToPatch) } projectCreate :: InputPattern @@ -2751,13 +3192,12 @@ projectCreate = [ ("`project.create`", "creates a project with a random name"), ("`project.create foo`", "creates a project named `foo`") ], - parse = - unifyArguments `andThen` \case - [name] -> - case tryInto @ProjectName (Text.pack name) of - Left _ -> Left "Invalid project name." - Right name1 -> Right (Input.ProjectCreateI True (Just name1)) - _ -> Right (Input.ProjectCreateI True Nothing) + parse = \case + [] -> Right (Input.ProjectCreateI True Nothing) + [name] -> + bimap P.text (Input.ProjectCreateI True . pure) $ + handleProjectArg name + _ -> Left $ showPatternHelp projectCreate } projectCreateEmptyInputPattern :: InputPattern @@ -2772,13 +3212,12 @@ projectCreateEmptyInputPattern = [ ("`project.create-empty`", "creates an empty project with a random name"), ("`project.create-empty foo`", "creates an empty project named `foo`") ], - parse = - unifyArguments `andThen` \case - [name] -> - case tryInto @ProjectName (Text.pack name) of - Left _ -> Left "Invalid project name." - Right name1 -> Right (Input.ProjectCreateI False (Just name1)) - _ -> Right (Input.ProjectCreateI False Nothing) + parse = \case + [] -> Right (Input.ProjectCreateI False Nothing) + [name] -> + bimap P.text (Input.ProjectCreateI False . pure) $ + handleProjectArg name + _ -> Left $ showPatternHelp projectCreateEmptyInputPattern } projectRenameInputPattern :: InputPattern @@ -2792,10 +3231,10 @@ projectRenameInputPattern = P.wrapColumn2 [ ("`project.rename foo`", "renames the current project to `foo`") ], - parse = - unifyArguments `andThen` \case - [nameString] | Right name <- tryInto (Text.pack nameString) -> Right (Input.ProjectRenameI name) - _ -> Left (showPatternHelp projectRenameInputPattern) + parse = \case + [nameString] -> + bimap P.text Input.ProjectRenameI $ handleProjectArg nameString + _ -> Left (showPatternHelp projectRenameInputPattern) } projectSwitch :: InputPattern @@ -2812,13 +3251,11 @@ projectSwitch = ("`switch foo/`", "switches to the last branch you visited in the project `foo`"), ("`switch /bar`", "switches to the branch `bar` in the current project") ], - parse = - unifyArguments `andThen` \case - [name] -> - case tryInto @ProjectAndBranchNames (Text.pack name) of - Left _ -> Left (showPatternHelp projectSwitch) - Right projectAndBranch -> Right (Input.ProjectSwitchI projectAndBranch) - _ -> Left (showPatternHelp projectSwitch) + parse = \case + [name] -> + bimap P.text Input.ProjectSwitchI $ + handleProjectAndBranchNamesArg name + _ -> Left (showPatternHelp projectSwitch) } where suggestionsConfig = @@ -2851,11 +3288,11 @@ branchesInputPattern = [ ("`branches`", "lists all branches in the current project"), ("`branches foo", "lists all branches in the project `foo`") ], - parse = - unifyArguments `andThen` \case - [] -> Right (Input.BranchesI Nothing) - [nameString] | Right name <- tryFrom (Text.pack nameString) -> Right (Input.BranchesI (Just name)) - _ -> Left (showPatternHelp branchesInputPattern) + parse = \case + [] -> Right (Input.BranchesI Nothing) + [nameString] -> + bimap P.text (Input.BranchesI . pure) $ handleProjectArg nameString + _ -> Left (showPatternHelp branchesInputPattern) } branchInputPattern :: InputPattern @@ -2874,25 +3311,17 @@ branchInputPattern = ("`branch /bar foo`", "forks the branch `bar` of the current project to a new branch `foo`"), ("`branch .bar foo`", "forks the path `.bar` of the current project to a new branch `foo`") ], - parse = - maybeToEither (showPatternHelp branchInputPattern) - . ( \case - [source0, name] -> do - source <- parseLooseCodeOrProject source0 - projectAndBranch <- - Text.pack name - & tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) - & eitherToMaybe - Just (Input.BranchI (Input.BranchSourceI'LooseCodeOrProject source) projectAndBranch) - [name] -> do - projectAndBranch <- - Text.pack name - & tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) - & eitherToMaybe - Just (Input.BranchI Input.BranchSourceI'CurrentContext projectAndBranch) - _ -> Nothing - ) - . unifyArguments + parse = \case + [source0, name] -> + first P.text $ + Input.BranchI . Input.BranchSourceI'LooseCodeOrProject + <$> handleLooseCodeOrProjectArg source0 + <*> handleProjectAndBranchArg name + [name] -> + first P.text $ + Input.BranchI Input.BranchSourceI'CurrentContext + <$> handleProjectAndBranchArg name + _ -> Left $ showPatternHelp branchInputPattern } where newBranchNameArg = @@ -2916,13 +3345,11 @@ branchEmptyInputPattern = visibility = I.Visible, args = [], help = P.wrap "Create a new empty branch.", - parse = - unifyArguments `andThen` \case - [name] -> - first (\_ -> showPatternHelp branchEmptyInputPattern) do - projectAndBranch <- tryInto @(ProjectAndBranch (Maybe ProjectName) ProjectBranchName) (Text.pack name) - Right (Input.BranchI Input.BranchSourceI'Empty projectAndBranch) - _ -> Left (showPatternHelp branchEmptyInputPattern) + parse = \case + [name] -> + bimap P.text (Input.BranchI Input.BranchSourceI'Empty) $ + handleProjectAndBranchArg name + _ -> Left (showPatternHelp branchEmptyInputPattern) } branchRenameInputPattern :: InputPattern @@ -2934,12 +3361,11 @@ branchRenameInputPattern = args = [], help = P.wrapColumn2 - [ ("`branch.rename foo`", "renames the current branch to `foo`") - ], - parse = - unifyArguments `andThen` \case - [nameString] | Right name <- tryInto (Text.pack nameString) -> Right (Input.BranchRenameI name) - _ -> Left (showPatternHelp branchRenameInputPattern) + [("`branch.rename foo`", "renames the current branch to `foo`")], + parse = \case + [name] -> + bimap P.text Input.BranchRenameI $ handleProjectBranchNameArg name + _ -> Left (showPatternHelp branchRenameInputPattern) } clone :: InputPattern @@ -2971,19 +3397,18 @@ clone = <> P.group (makeExample helpTopics ["remotes"] <> ")") ) ], - parse = - maybe (Left (showPatternHelp clone)) Right - . ( \case - [remoteNamesString] -> do - remoteNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack remoteNamesString)) - Just (Input.CloneI remoteNames Nothing) - [remoteNamesString, localNamesString] -> do - remoteNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack remoteNamesString)) - localNames <- eitherToMaybe (tryInto @ProjectAndBranchNames (Text.pack localNamesString)) - Just (Input.CloneI remoteNames (Just localNames)) - _ -> Nothing - ) - . unifyArguments + parse = \case + [remoteNames] -> do + first P.text $ + Input.CloneI + <$> handleProjectAndBranchNamesArg remoteNames + <*> pure Nothing + [remoteNames, localNames] -> + first P.text $ + Input.CloneI + <$> handleProjectAndBranchNamesArg remoteNames + <*> fmap pure (handleProjectAndBranchNamesArg localNames) + _ -> Left $ showPatternHelp clone } releaseDraft :: InputPattern @@ -2994,10 +3419,13 @@ releaseDraft = visibility = I.Visible, args = [], help = P.wrap "Draft a release.", - parse = - unifyArguments `andThen` \case - [tryInto @Semver . Text.pack -> Right semver] -> Right (Input.ReleaseDraftI semver) - _ -> Left (showPatternHelp releaseDraft) + parse = \case + [semverString] -> + bimap (const "Couldn’t parse version number") Input.ReleaseDraftI + . tryInto @Semver + . Text.pack + =<< unsupportedStructuredArgument "a version number" semverString + _ -> Left (showPatternHelp releaseDraft) } upgrade :: InputPattern @@ -3010,23 +3438,14 @@ upgrade = help = P.wrap $ "`upgrade old new` upgrades library dependency `lib.old` to `lib.new`, and, if successful, deletes `lib.old`.", - parse = - maybeToEither (I.help upgrade) - . ( \args -> do - [oldString, newString] <- Just args - old <- parseRelativeNameSegment oldString - new <- parseRelativeNameSegment newString - Just (Input.UpgradeI old new) - ) - . unifyArguments + parse = \case + [oldString, newString] -> + first P.text $ + Input.UpgradeI + <$> handleRelativeNameSegmentArg oldString + <*> handleRelativeNameSegmentArg newString + _ -> Left $ I.help upgrade } - where - parseRelativeNameSegment :: String -> Maybe NameSegment - parseRelativeNameSegment string = do - name <- Name.parseText (Text.pack string) - guard (Name.isRelative name) - segment NE.:| [] <- Just (Name.reverseSegments name) - Just segment validInputs :: [InputPattern] validInputs = @@ -3347,7 +3766,7 @@ data ProjectInclusion = OnlyWithinCurrentProject | OnlyOutsideCurrentProject | A data BranchInclusion = ExcludeCurrentBranch | AllBranches deriving stock (Eq, Ord, Show) -projectsByPrefix :: MonadIO m => ProjectInclusion -> Codebase m v a -> Path.Absolute -> Text -> m [(ProjectId, ProjectName)] +projectsByPrefix :: (MonadIO m) => ProjectInclusion -> Codebase m v a -> Path.Absolute -> Text -> m [(ProjectId, ProjectName)] projectsByPrefix projectInclusion codebase path query = do allProjectMatches <- Codebase.runTransaction codebase do Queries.loadAllProjectsBeginningWith (Just query) @@ -3427,7 +3846,7 @@ projectAndOrBranchSuggestions config inputStr codebase _httpClient path = do ProjectBranchPath projectId branchId _ -> (Just projectId, Just branchId) handleAmbiguousComplete :: - MonadIO m => + (MonadIO m) => Text -> Codebase m v a -> m [Completion] @@ -3516,7 +3935,7 @@ projectAndOrBranchSuggestions config inputStr codebase _httpClient path = do then projectCompletions else branchCompletions ++ projectCompletions - handleBranchesComplete :: MonadIO m => Text -> Codebase m v a -> Path.Absolute -> m [Completion] + handleBranchesComplete :: (MonadIO m) => Text -> Codebase m v a -> Path.Absolute -> m [Completion] handleBranchesComplete branchName codebase path = do branches <- case preview ProjectUtils.projectBranchPathPrism path of @@ -3557,7 +3976,7 @@ projectBranchToCompletion projectName (_, branchName) = } handleBranchesComplete :: - MonadIO m => + (MonadIO m) => ProjectBranchSuggestionsConfig -> Text -> Codebase m v a -> @@ -3593,7 +4012,7 @@ currentProjectBranchToCompletion (_, branchName) = } branchRelativePathSuggestions :: - MonadIO m => + (MonadIO m) => ProjectBranchSuggestionsConfig -> String -> Codebase m v a -> @@ -3699,7 +4118,7 @@ branchRelativePathSuggestions config inputStr codebase _httpClient currentPath = branchPathSepPretty = P.hiBlack branchPathSep - branchPathSep :: IsString s => s + branchPathSep :: (IsString s) => s branchPathSep = ":" -- | A project name, branch name, or both. @@ -3758,7 +4177,7 @@ data OptionalSlash | NoSlash projectNameSuggestions :: - MonadIO m => + (MonadIO m) => OptionalSlash -> String -> Codebase m v a -> @@ -3789,21 +4208,16 @@ parsePullSource = Megaparsec.parseMaybe (readRemoteNamespaceParser ProjectBranchSpecifier'NameOrLatestRelease) -- | Parse a 'Input.PushSource'. -parsePushSource :: String -> Either (P.Pretty CT.ColorText) Input.PushSource +parsePushSource :: String -> Maybe Input.PushSource parsePushSource sourceStr = - case tryFrom (Text.pack sourceStr) of - Left _ -> - case Path.parsePath' sourceStr of - Left _ -> Left (I.help push) - Right path -> Right (Input.PathySource path) - Right branch -> Right (Input.ProjySource branch) + fixup Input.ProjySource (tryFrom $ Text.pack sourceStr) + <|> fixup Input.PathySource (Path.parsePath' sourceStr) + where + fixup = either (const Nothing) . (pure .) -- | Parse a push target. -parsePushTarget :: String -> Either (P.Pretty CT.ColorText) (WriteRemoteNamespace (These ProjectName ProjectBranchName)) -parsePushTarget target = - case Megaparsec.parseMaybe UriParser.writeRemoteNamespace (Text.pack target) of - Nothing -> Left (I.help push) - Just path -> Right path +parsePushTarget :: String -> Maybe (WriteRemoteNamespace (These ProjectName ProjectBranchName)) +parsePushTarget = Megaparsec.parseMaybe UriParser.writeRemoteNamespace . Text.pack parseHashQualifiedName :: String -> Either (P.Pretty CT.ColorText) (HQ.HashQualified Name) diff --git a/unison-cli/src/Unison/CommandLine/Main.hs b/unison-cli/src/Unison/CommandLine/Main.hs index 18a0c8f9c86..f675f918967 100644 --- a/unison-cli/src/Unison/CommandLine/Main.hs +++ b/unison-cli/src/Unison/CommandLine/Main.hs @@ -113,7 +113,7 @@ getUserInput codebase authHTTPClient currentPath numberedArgs = -- Ctrl-c or some input cancel, re-run the prompt go Right (Just (expandedArgs, i)) -> do - let expandedArgs' = IP.unifyArguments expandedArgs + let expandedArgs' = IP.unifyArgument <$> expandedArgs expandedArgsStr = unwords expandedArgs' when (expandedArgs' /= ws) $ do liftIO . putStrLn $ fullPrompt <> expandedArgsStr From d6a394f3e0698d2fd711ec375fd0c1d3eb6141ba Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 21 May 2024 00:54:16 -0600 Subject: [PATCH 05/10] Serialize `StructuredArgument`s on demand Previously, the `Text` format had been preserved from the original code. This extracts all to a separate function that is called as needed. All transcripts still pass. --- .../src/Unison/Codebase/Editor/HandleInput.hs | 34 +-- .../Editor/HandleInput/FindAndReplace.hs | 3 +- .../src/Unison/Codebase/Editor/Output.hs | 2 +- .../src/Unison/CommandLine/InputPattern.hs | 2 +- .../src/Unison/CommandLine/InputPatterns.hs | 248 +++++++++++------- .../src/Unison/CommandLine/OutputMessages.hs | 64 ++--- 6 files changed, 185 insertions(+), 168 deletions(-) diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index 4886caeb481..312c8c3437f 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -153,7 +153,6 @@ import Unison.Reference qualified as Reference import Unison.Referent (Referent) import Unison.Referent qualified as Referent import Unison.Runtime.IOSource qualified as IOSource -import Unison.Server.Backend (ShallowListEntry (..)) import Unison.Server.Backend qualified as Backend import Unison.Server.CodebaseServer qualified as Server import Unison.Server.Doc.Markdown.Render qualified as Md @@ -298,8 +297,8 @@ loop e = do let (shortEntries, numberedEntries) = unzip $ expandedEntries <&> \(time, hash, reason) -> - let ((exp, txt), sa) = ((id &&& ("#" <>) . SCH.toText) . SCH.fromHash schLength &&& SA.Namespace) hash - in ((time, exp, reason), (txt, sa)) + let (exp, sa) = (SCH.fromHash schLength &&& SA.Namespace) hash + in ((time, exp, reason), sa) Cli.setNumberedArgs numberedEntries Cli.respond $ ShowReflog shortEntries where @@ -791,13 +790,13 @@ loop e = do (seg, _) <- Map.toList (Branch._edits b) ] Cli.respond $ ListOfPatches $ Set.fromList patches - Cli.setNumberedArgs $ fmap (Name.toText &&& SA.Name) patches + Cli.setNumberedArgs $ fmap SA.Name patches FindShallowI pathArg -> do Cli.Env {codebase} <- ask pathArgAbs <- Cli.resolvePath' pathArg entries <- liftIO (Backend.lsAtPath codebase Nothing pathArgAbs) - Cli.setNumberedArgs $ fmap (entryToHQText &&& SA.ShallowListEntry pathArg) entries + Cli.setNumberedArgs $ fmap (SA.ShallowListEntry pathArg) entries pped <- Cli.currentPrettyPrintEnvDecl let suffixifiedPPE = PPED.suffixifiedPPE pped -- This used to be a delayed action which only forced the loading of the root @@ -807,21 +806,6 @@ loop e = do -- in an improvement, so perhaps it's not worth the effort. let buildPPE = pure suffixifiedPPE Cli.respond $ ListShallow buildPPE entries - where - entryToHQText :: ShallowListEntry v Ann -> Text - entryToHQText e = - fixup $ case e of - ShallowTypeEntry te -> Backend.typeEntryDisplayName te - ShallowTermEntry te -> Backend.termEntryDisplayName te - ShallowBranchEntry ns _ _ -> NameSegment.toEscapedText ns - ShallowPatchEntry ns -> NameSegment.toEscapedText ns - where - fixup s = - pathArgStr - <> if Text.null pathArgStr || Text.isSuffixOf "." pathArgStr - then s - else "." <> s - pathArgStr = Text.pack $ show pathArg FindI isVerbose fscope ws -> handleFindI isVerbose fscope ws input StructuredFindI _fscope ws -> handleStructuredFindI ws StructuredFindReplaceI ws -> handleStructuredFindReplaceI ws @@ -1501,7 +1485,7 @@ handleFindI isVerbose fscope ws input = do (mapMaybe (HQ.parseTextWith anythingBeforeHash . Text.pack) qs) pure $ uniqueBy SR.toReferent srs let respondResults results = do - Cli.setNumberedArgs $ fmap (HQ.toText . IP.searchResultToHQ searchRoot &&& SA.SearchResult searchRoot) results + Cli.setNumberedArgs $ fmap (SA.SearchResult searchRoot) results results' <- Cli.runTransaction (Backend.loadSearchResults codebase results) Cli.respond $ ListOfDefinitions fscope suffixifiedPPE isVerbose results' results <- getResults names @@ -1556,8 +1540,8 @@ handleDependencies hq = do let types = nubOrdOn snd . Name.sortByText (HQ.toText . fst) $ (join $ fst <$> results) let terms = nubOrdOn snd . Name.sortByText (HQ.toText . fst) $ (join $ snd <$> results) Cli.setNumberedArgs $ - map ((Reference.toText &&& SA.Ref) . snd) types - <> map ((Reference.toText &&& SA.Ref) . Referent.toReference . snd) terms + map (SA.Ref . snd) types + <> map (SA.Ref . Referent.toReference . snd) terms Cli.respond $ ListDependencies suffixifiedPPE lds (fst <$> types) (fst <$> terms) handleDependents :: HQ.HashQualified Name -> Cli () @@ -1594,7 +1578,7 @@ handleDependents hq = do let sort = nubOrdOn snd . Name.sortByText (HQ.toText . fst) let types = sort [(n, r) | (False, n, r) <- join results] let terms = sort [(n, r) | (True, n, r) <- join results] - Cli.setNumberedArgs . map ((Reference.toText &&& SA.Ref) . view _2) $ types <> terms + Cli.setNumberedArgs . map (SA.Ref . view _2) $ types <> terms Cli.respond (ListDependents ppe lds (fst <$> types) (fst <$> terms)) handleDiffNamespaceToPatch :: Text -> DiffNamespaceToPatchInput -> Cli () @@ -1775,7 +1759,7 @@ doShowTodoOutput patch scopePath = do then Cli.respond NoConflictsOrEdits else do Cli.setNumberedArgs - ((Reference.toText &&& SA.Ref) . view _2 <$> fst (TO.todoFrontierDependents todo)) + (SA.Ref . view _2 <$> fst (TO.todoFrontierDependents todo)) pped <- Cli.currentPrettyPrintEnvDecl Cli.respondNumbered $ TodoOutput pped todo diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs index 9ad17bbcc6a..f96ae85b217 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput/FindAndReplace.hs @@ -4,7 +4,6 @@ module Unison.Codebase.Editor.HandleInput.FindAndReplace ) where -import Control.Arrow ((&&&)) import Control.Lens hiding (at) import Control.Monad.Reader (ask) import Control.Monad.State @@ -89,7 +88,7 @@ handleStructuredFindI rule = do ok t = pure (t, False) results0 <- traverse ok results let results = Alphabetical.sortAlphabeticallyOn fst [(hq, r) | ((hq, r), True) <- results0] - let toNumArgs = (Reference.toText &&& SA.Ref) . Referent.toReference . view _2 + let toNumArgs = SA.Ref . Referent.toReference . view _2 Cli.setNumberedArgs $ map toNumArgs results Cli.respond (ListStructuredFind (fst <$> results)) diff --git a/unison-cli/src/Unison/Codebase/Editor/Output.hs b/unison-cli/src/Unison/Codebase/Editor/Output.hs index 751292ba92b..88ad7f20438 100644 --- a/unison-cli/src/Unison/Codebase/Editor/Output.hs +++ b/unison-cli/src/Unison/Codebase/Editor/Output.hs @@ -90,7 +90,7 @@ type SourceName = Text -- __NB__: This only temporarily holds `Text`. Until all of the inputs are -- updated to handle `StructuredArgument`s, we need to ensure that the -- serialization remains unchanged. -type NumberedArgs = [(Text, StructuredArgument)] +type NumberedArgs = [StructuredArgument] type HashLength = Int diff --git a/unison-cli/src/Unison/CommandLine/InputPattern.hs b/unison-cli/src/Unison/CommandLine/InputPattern.hs index 15f58eb73b7..4014bc1dc76 100644 --- a/unison-cli/src/Unison/CommandLine/InputPattern.hs +++ b/unison-cli/src/Unison/CommandLine/InputPattern.hs @@ -51,7 +51,7 @@ data Visibility = Hidden | Visible -- needs to be parsed or a numbered argument that doesn’t need to be parsed, as -- we’ve preserved its representation (although the numbered argument could -- still be of the wrong type, which should result in an error). -type Argument = Either String (Text, StructuredArgument) +type Argument = Either String StructuredArgument type Arguments = [Argument] diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index ebae14662ad..247df1ce2c7 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -41,7 +41,7 @@ import Unison.Codebase.Editor.StructuredArgument (StructuredArgument) import Unison.Codebase.Editor.StructuredArgument qualified as SA import Unison.Codebase.Editor.UriParser (readRemoteNamespaceParser) import Unison.Codebase.Editor.UriParser qualified as UriParser -import Unison.Codebase.Path (Path) +import Unison.Codebase.Path (Path, Path') import Unison.Codebase.Path qualified as Path import Unison.Codebase.Path.Parse qualified as Path import Unison.Codebase.PushBehavior qualified as PushBehavior @@ -63,16 +63,20 @@ import Unison.Name (Name) import Unison.Name qualified as Name import Unison.NameSegment (NameSegment) import Unison.NameSegment qualified as NameSegment +import Unison.Parser.Ann (Ann) import Unison.Prelude import Unison.Project (ProjectAndBranch (..), ProjectAndBranchNames (..), ProjectBranchName, ProjectBranchNameOrLatestRelease (..), ProjectBranchSpecifier (..), ProjectName, Semver) import Unison.Project.Util (ProjectContext (..), projectContextFromPath) import Unison.Reference qualified as Reference import Unison.Referent qualified as Referent +import Unison.Server.Backend (ShallowListEntry (..)) +import Unison.Server.Backend qualified as Backend import Unison.Server.SearchResult (SearchResult) import Unison.Server.SearchResult qualified as SR import Unison.ShortHash (ShortHash) -import Unison.Syntax.HashQualified qualified as HQ (parseText) -import Unison.Syntax.Name qualified as Name (parseTextEither) +import Unison.Syntax.HashQualified qualified as HQ (parseText, toText) +import Unison.Syntax.Name qualified as Name (parseTextEither, toText) +import Unison.Syntax.NameSegment qualified as NameSegment import Unison.Util.ColorText qualified as CT import Unison.Util.Monoid (intercalateMap) import Unison.Util.Pretty qualified as P @@ -83,13 +87,60 @@ import Unison.Util.Pretty qualified as P schLength :: Int schLength = 10 --- | --- --- __TODO__: Temporary. This may stick around in a handful of cases, but it’ll --- have to actually serialize the `StructuredArgument` rather than --- relying on the parallel `Text`. +formatStructuredArgument :: StructuredArgument -> Text +formatStructuredArgument = \case + SA.AbsolutePath path -> into @Text $ show path + SA.Name name -> Name.toText name + SA.HashQualified hqName -> HQ.toText hqName + SA.Project projectName -> into @Text projectName + SA.ProjectBranch (ProjectAndBranch mproj branch) -> + maybe + (Text.cons '/' . into @Text) + (\project -> into @Text . ProjectAndBranch project) + mproj + branch + SA.Ref reference -> + -- also: ShortHash.toText . Reference.toShortHash + Reference.toText reference + SA.Namespace causalHash -> + -- also: ("#" <>) . Hash.toBase32HexText . unCausalHash + ("#" <>) . SCH.toText $ SCH.fromHash schLength causalHash + SA.NameWithBranchPrefix absBranchId name -> + prefixBranchId absBranchId name + SA.HashQualifiedWithBranchPrefix absBranchId hq'Name -> + HQ'.toTextWith (prefixBranchId absBranchId) hq'Name + SA.ShallowListEntry path entry -> entryToHQText path entry + SA.SearchResult searchRoot searchResult -> + HQ.toText $ searchResultToHQ searchRoot searchResult + where + -- E.g. + -- prefixBranchId "#abcdef" "base.List.map" -> "#abcdef:.base.List.map" + -- prefixBranchId ".base" "List.map" -> ".base.List.map" + prefixBranchId :: Input.AbsBranchId -> Name -> Text + prefixBranchId branchId name = case branchId of + Left sch -> "#" <> SCH.toText sch <> ":" <> Name.toText (Name.makeAbsolute name) + Right pathPrefix -> Name.toText (Name.makeAbsolute . Path.prefixName pathPrefix $ name) + + entryToHQText :: Path' -> ShallowListEntry v Ann -> Text + entryToHQText pathArg e = + fixup $ case e of + ShallowTypeEntry te -> Backend.typeEntryDisplayName te + ShallowTermEntry te -> Backend.termEntryDisplayName te + ShallowBranchEntry ns _ _ -> NameSegment.toEscapedText ns + ShallowPatchEntry ns -> NameSegment.toEscapedText ns + where + fixup s = + pathArgStr + <> if Text.null pathArgStr || Text.isSuffixOf "." pathArgStr + then s + else "." <> s + pathArgStr = Text.pack $ show pathArg + +-- | Converts an arbitrary argument to a `String`. This is for cases where the +-- command /should/ accept a structured argument of some type, but currently +-- wants a `String`. unifyArgument :: I.Argument -> String -unifyArgument = either id (Text.unpack . fst) +unifyArgument = either id (Text.unpack . formatStructuredArgument) showPatternHelp :: InputPattern -> P.Pretty CT.ColorText showPatternHelp i = @@ -130,11 +181,11 @@ expectedButActually expected actualValue actualType = <> actualType <> "." -wrongStructuredArgument :: Text -> (Text, StructuredArgument) -> Text -wrongStructuredArgument expected (actualStr, actual) = +wrongStructuredArgument :: Text -> StructuredArgument -> Text +wrongStructuredArgument expected actual = expectedButActually expected - actualStr + (formatStructuredArgument actual) case actual of SA.Ref _ -> "a reference" SA.Name _ -> "a name" @@ -179,9 +230,9 @@ handleProjectArg = $ Text.pack name ) ( \case - (_, SA.Project project) -> pure project + SA.Project project -> pure project -- __FIXME__: Do we want to treat a project branch as a project? - (_, SA.ProjectBranch (ProjectAndBranch (Just project) _)) -> pure project + SA.ProjectBranch (ProjectAndBranch (Just project) _) -> pure project otherArgType -> Left $ wrongStructuredArgument "a path" otherArgType ) @@ -193,8 +244,8 @@ handleLooseCodeOrProjectArg = . parseLooseCodeOrProject ) ( \case - (_, SA.AbsolutePath path) -> pure . This $ Path.absoluteToPath' path - (_, SA.ProjectBranch pb) -> pure $ That pb + SA.AbsolutePath path -> pure . This $ Path.absoluteToPath' path + SA.ProjectBranch pb -> pure $ That pb otherArgType -> Left $ wrongStructuredArgument "a path or project branch" otherArgType ) @@ -211,7 +262,7 @@ handleProjectAndBranchArg = $ Text.pack name ) ( \case - (_, SA.ProjectBranch pb) -> pure pb + SA.ProjectBranch pb -> pure pb otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType ) @@ -221,16 +272,16 @@ handleHashQualifiedNameArg = either parseHashQualifiedName ( \case - (_, SA.Name name) -> pure $ HQ.NameOnly name - (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ HQ.NameOnly name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.Name name -> pure $ HQ.NameOnly name + SA.NameWithBranchPrefix (Left _) name -> pure $ HQ.NameOnly name + SA.NameWithBranchPrefix (Right prefix) name -> pure . HQ.NameOnly . Name.makeAbsolute $ Path.prefixName prefix name - (_, SA.Ref ref) -> pure . HQ.HashOnly $ Reference.toShortHash ref - (_, SA.HashQualified hqname) -> pure hqname - (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ HQ'.toHQ hqname - (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + SA.Ref ref -> pure . HQ.HashOnly $ Reference.toShortHash ref + SA.HashQualified hqname -> pure hqname + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ HQ'.toHQ hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> pure . HQ'.toHQ $ Name.makeAbsolute . Path.prefixName prefix <$> hqname - (_, SA.SearchResult mpath result) -> pure $ searchResultToHQ mpath result + SA.SearchResult mpath result -> pure $ searchResultToHQ mpath result otherArgType -> Left . P.text $ wrongStructuredArgument "a hash-qualified name" otherArgType ) @@ -240,9 +291,9 @@ handlePathArg = either Path.parsePath \case - (_, SA.Name name) -> pure $ Path.fromName name - (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ Path.fromName name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.Name name -> pure $ Path.fromName name + SA.NameWithBranchPrefix (Left _) name -> pure $ Path.fromName name + SA.NameWithBranchPrefix (Right prefix) name -> pure . Path.fromName $ Path.prefixName prefix name otherArgType -> Left $ wrongStructuredArgument "a relative path" otherArgType @@ -252,10 +303,10 @@ handlePath'Arg = either Path.parsePath' ( \case - (_, SA.AbsolutePath path) -> pure $ Path.absoluteToPath' path - (_, SA.Name name) -> pure $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.AbsolutePath path -> pure $ Path.absoluteToPath' path + SA.Name name -> pure $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure $ Path.fromName' name + SA.NameWithBranchPrefix (Right prefix) name -> pure . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name otherArgType -> Left $ wrongStructuredArgument "a namespace" otherArgType ) @@ -277,9 +328,9 @@ handleSplit'Arg = either Path.parseSplit' ( \case - (_, SA.Name name) -> pure $ Path.splitFromName' name - (_, SA.NameWithBranchPrefix (Left _) name) -> pure $ Path.splitFromName' name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.Name name -> pure $ Path.splitFromName' name + SA.NameWithBranchPrefix (Left _) name -> pure $ Path.splitFromName' name + SA.NameWithBranchPrefix (Right prefix) name -> pure . Path.splitFromName' . Name.makeAbsolute $ Path.prefixName prefix name otherNumArg -> Left $ wrongStructuredArgument "a split name" otherNumArg ) @@ -292,7 +343,7 @@ handleProjectBranchNameArg = either (first (const "Wanted a branch name, but it wasn’t") . tryInto . Text.pack) ( \case - (_, SA.ProjectBranch (ProjectAndBranch _ branch)) -> pure branch + SA.ProjectBranch (ProjectAndBranch _ branch) -> pure branch otherNumArg -> Left $ wrongStructuredArgument "a branch name" otherNumArg ) @@ -301,14 +352,12 @@ handleBranchIdArg = either Input.parseBranchId ( \case - (_, SA.AbsolutePath path) -> pure . pure $ Path.absoluteToPath' path - (_, SA.Name name) -> pure . pure $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Left _) name) -> - pure . pure $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> - pure . pure . Path.fromName' . Name.makeAbsolute $ - Path.prefixName prefix name - (_, SA.Namespace hash) -> pure . Left $ SCH.fromHash schLength hash + SA.AbsolutePath path -> pure . pure $ Path.absoluteToPath' path + SA.Name name -> pure . pure $ Path.fromName' name + SA.NameWithBranchPrefix mprefix name -> + pure . pure . Path.fromName' $ + either (const name) (Name.makeAbsolute . flip Path.prefixName name) mprefix + SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash otherNumArg -> Left $ wrongStructuredArgument "a branch id" otherNumArg ) @@ -326,17 +375,16 @@ handleBranchIdOrProjectArg = . branchIdOrProject ) ( \case - (_, SA.Namespace hash) -> - pure . This . Left $ SCH.fromHash schLength hash - (_, SA.AbsolutePath path) -> + SA.Namespace hash -> pure . This . Left $ SCH.fromHash schLength hash + SA.AbsolutePath path -> pure . This . pure $ Path.absoluteToPath' path - (_, SA.Name name) -> pure . This . pure $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Left _) name) -> + SA.Name name -> pure . This . pure $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . This . pure $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.NameWithBranchPrefix (Right prefix) name -> pure . This . pure . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name - (_, SA.ProjectBranch pb) -> pure $ pure pb + SA.ProjectBranch pb -> pure $ pure pb otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType ) where @@ -364,16 +412,16 @@ handleBranchId2Arg = either Input.parseBranchId2 ( \case - (_, SA.Namespace hash) -> pure . Left $ SCH.fromHash schLength hash - (_, SA.AbsolutePath path) -> + SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash + SA.AbsolutePath path -> pure . pure . LoosePath $ Path.absoluteToPath' path - (_, SA.Name name) -> pure . pure . LoosePath $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Left _) name) -> + SA.Name name -> pure . pure . LoosePath $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . pure . LoosePath $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.NameWithBranchPrefix (Right prefix) name -> pure . pure . LoosePath . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name - (_, SA.ProjectBranch (ProjectAndBranch mproject branch)) -> + SA.ProjectBranch (ProjectAndBranch mproject branch) -> pure . pure . BranchRelative . This $ maybe (Left branch) (pure . (,branch)) mproject otherNumArg -> @@ -386,14 +434,14 @@ handleBranchRelativePathArg = either parseBranchRelativePath ( \case - (_, SA.AbsolutePath path) -> pure . LoosePath $ Path.absoluteToPath' path - (_, SA.Name name) -> pure . LoosePath $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Left _) name) -> + SA.AbsolutePath path -> pure . LoosePath $ Path.absoluteToPath' path + SA.Name name -> pure . LoosePath $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . LoosePath $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.NameWithBranchPrefix (Right prefix) name -> pure . LoosePath . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name - (_, SA.ProjectBranch (ProjectAndBranch mproject branch)) -> + SA.ProjectBranch (ProjectAndBranch mproject branch) -> pure . BranchRelative . This $ maybe (Left branch) (pure . (,branch)) mproject otherNumArg -> @@ -427,11 +475,11 @@ handleHashQualifiedSplit'Arg = either Path.parseHQSplit' ( \case - (_, SA.HashQualified name) -> hqNameToSplit' name - (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ hq'NameToSplit' hqname - (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + SA.HashQualified name -> hqNameToSplit' name + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ hq'NameToSplit' hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> pure . hq'NameToSplit' $ Name.makeAbsolute . Path.prefixName prefix <$> hqname - (_, SA.SearchResult mpath result) -> hqNameToSplit' $ searchResultToHQ mpath result + SA.SearchResult mpath result -> hqNameToSplit' $ searchResultToHQ mpath result otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg ) @@ -440,11 +488,11 @@ handleHashQualifiedSplitArg = either Path.parseHQSplit ( \case - (_, SA.HashQualified name) -> hqNameToSplit name - (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ hq'NameToSplit hqname - (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + SA.HashQualified name -> hqNameToSplit name + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ hq'NameToSplit hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> pure . hq'NameToSplit $ Name.makeAbsolute . Path.prefixName prefix <$> hqname - (_, SA.SearchResult mpath result) -> hqNameToSplit $ searchResultToHQ mpath result + SA.SearchResult mpath result -> hqNameToSplit $ searchResultToHQ mpath result otherNumArg -> Left $ wrongStructuredArgument "a relative name" otherNumArg ) @@ -453,7 +501,7 @@ handleShortCausalHashArg = either (first Text.pack . Input.parseShortCausalHash) ( \case - (_, SA.Namespace hash) -> pure $ SCH.fromHash schLength hash + SA.Namespace hash -> pure $ SCH.fromHash schLength hash otherNumArg -> Left $ wrongStructuredArgument "a causal hash" otherNumArg ) @@ -463,12 +511,12 @@ handleShortHashOrHQSplit'Arg = either Path.parseShortHashOrHQSplit' ( \case - (_, SA.Ref ref) -> pure $ Left $ Reference.toShortHash ref - (_, SA.HashQualified name) -> pure <$> hqNameToSplit' name - (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure . pure $ hq'NameToSplit' hqname - (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + SA.Ref ref -> pure $ Left $ Reference.toShortHash ref + SA.HashQualified name -> pure <$> hqNameToSplit' name + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure . pure $ hq'NameToSplit' hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> pure . pure $ hq'NameToSplit' (Name.makeAbsolute . Path.prefixName prefix <$> hqname) - (_, SA.SearchResult mpath result) -> + SA.SearchResult mpath result -> fmap pure . hqNameToSplit' $ searchResultToHQ mpath result otherNumArg -> Left $ wrongStructuredArgument "a hash or name" otherNumArg ) @@ -486,16 +534,16 @@ handleNameArg = either (Name.parseTextEither . Text.pack) ( \case - (_, SA.Name name) -> pure name - (_, SA.NameWithBranchPrefix (Left _) name) -> pure name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.Name name -> pure name + SA.NameWithBranchPrefix (Left _) name -> pure name + SA.NameWithBranchPrefix (Right prefix) name -> pure . Name.makeAbsolute $ Path.prefixName prefix name - (_, SA.HashQualified hqname) -> + SA.HashQualified hqname -> maybe (Left "can’t find a name from the numbered arg") pure $ HQ.toName hqname - (_, SA.HashQualifiedWithBranchPrefix (Left _) hqname) -> pure $ HQ'.toName hqname - (_, SA.HashQualifiedWithBranchPrefix (Right prefix) hqname) -> + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ HQ'.toName hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> pure . Name.makeAbsolute . Path.prefixName prefix $ HQ'.toName hqname - (_, SA.SearchResult mpath result) -> + SA.SearchResult mpath result -> maybe (Left "can’t find a name from the numbered arg") pure . HQ.toName $ searchResultToHQ mpath result @@ -511,9 +559,9 @@ handlePullSourceArg = either (maybe (Left "not a pull source") pure . parsePullSource . Text.pack) ( \case - (_, SA.Project project) -> + SA.Project project -> pure . RemoteRepo.ReadShare'ProjectBranch $ This project - (_, SA.ProjectBranch (ProjectAndBranch project branch)) -> + SA.ProjectBranch (ProjectAndBranch project branch) -> pure . RemoteRepo.ReadShare'ProjectBranch . maybe That These project $ ProjectBranchNameOrLatestRelease'Name branch otherNumArg -> @@ -530,8 +578,8 @@ handlePushTargetArg = ) ( fmap RemoteRepo.WriteRemoteProjectBranch . \case - (_, SA.Project project) -> pure $ This project - (_, SA.ProjectBranch (ProjectAndBranch project branch)) -> + SA.Project project -> pure $ This project + SA.ProjectBranch (ProjectAndBranch project branch) -> pure $ maybe That These project branch otherNumArg -> Left $ wrongStructuredArgument "a source to push from" otherNumArg @@ -544,15 +592,15 @@ handlePushSourceArg = . parsePushSource ) ( \case - (_, SA.AbsolutePath path) -> pure . Input.PathySource $ Path.absoluteToPath' path - (_, SA.Name name) -> pure . Input.PathySource $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Left _) name) -> + SA.AbsolutePath path -> pure . Input.PathySource $ Path.absoluteToPath' path + SA.Name name -> pure . Input.PathySource $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . Input.PathySource $ Path.fromName' name - (_, SA.NameWithBranchPrefix (Right prefix) name) -> + SA.NameWithBranchPrefix (Right prefix) name -> pure . Input.PathySource . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name - (_, SA.Project project) -> pure . Input.ProjySource $ This project - (_, SA.ProjectBranch (ProjectAndBranch project branch)) -> + SA.Project project -> pure . Input.ProjySource $ This project + SA.ProjectBranch (ProjectAndBranch project branch) -> pure . Input.ProjySource . maybe That These project $ branch otherNumArg -> Left $ wrongStructuredArgument "a source to push from" otherNumArg @@ -566,8 +614,8 @@ handleProjectAndBranchNamesArg = . Text.pack ) ( fmap ProjectAndBranchNames'Unambiguous . \case - (_, SA.Project project) -> pure $ This project - (_, SA.ProjectBranch (ProjectAndBranch mproj branch)) -> + SA.Project project -> pure $ This project + SA.ProjectBranch (ProjectAndBranch mproj branch) -> pure $ maybe That These mproj branch otherNumArg -> Left $ wrongStructuredArgument "a project or branch" otherNumArg @@ -2950,8 +2998,9 @@ execute = ) $ \case main : args -> - Input.ExecuteI (Text.pack $ unifyArgument main) - <$> traverse (unsupportedStructuredArgument "a command-line argument") args + Input.ExecuteI + <$> handleHashQualifiedNameArg main + <*> traverse (unsupportedStructuredArgument "a command-line argument") args _ -> Left $ showPatternHelp execute saveExecuteResult :: InputPattern @@ -3041,8 +3090,9 @@ runScheme = ) $ \case main : args -> - Input.ExecuteSchemeI (Text.pack $ unifyArgument main) - <$> traverse (unsupportedStructuredArgument "a command-line argument") args + Input.ExecuteSchemeI + <$> handleHashQualifiedNameArg main + <*> traverse (unsupportedStructuredArgument "a command-line argument") args _ -> Left $ showPatternHelp runScheme compileScheme :: InputPattern diff --git a/unison-cli/src/Unison/CommandLine/OutputMessages.hs b/unison-cli/src/Unison/CommandLine/OutputMessages.hs index af5b1fa1c59..0d9fe8e7c7d 100644 --- a/unison-cli/src/Unison/CommandLine/OutputMessages.hs +++ b/unison-cli/src/Unison/CommandLine/OutputMessages.hs @@ -5,7 +5,6 @@ module Unison.CommandLine.OutputMessages where -import Control.Arrow ((&&&)) import Control.Lens hiding (at) import Control.Monad.State import Control.Monad.State.Strict qualified as State @@ -130,7 +129,6 @@ import Unison.Server.Backend qualified as Backend import Unison.Server.SearchResult' qualified as SR' import Unison.Share.Sync qualified as Share import Unison.Share.Sync.Types (CodeserverTransportError (..)) -import Unison.ShortHash qualified as ShortHash import Unison.Sync.Types qualified as Share import Unison.Syntax.DeclPrinter qualified as DeclPrinter import Unison.Syntax.HashQualified qualified as HQ (toText, unsafeFromVar) @@ -351,7 +349,7 @@ notifyNumbered = \case ] branchHashes :: [CausalHash] branchHashes = (fst <$> reversedHistory) <> tailHashes - in (msg, (displayBranchHash &&& SA.Namespace) <$> branchHashes) + in (msg, SA.Namespace <$> branchHashes) where toSCH :: CausalHash -> ShortCausalHash toSCH h = SCH.fromHash schLength h @@ -410,7 +408,7 @@ notifyNumbered = \case ListEdits patch ppe -> showListEdits patch ppe ListProjects projects -> ( P.numberedList (map (prettyProjectName . view #name) projects), - map ((into @Text &&& SA.Project) . view #name) projects + map (SA.Project . view #name) projects ) ListBranches projectName branches -> ( P.columnNHeader @@ -427,11 +425,7 @@ notifyNumbered = \case : map (\branch -> ["", "", prettyRemoteBranchInfo branch]) remoteBranches ), map - ( ( (into @Text . ProjectAndBranch projectName) - &&& (SA.ProjectBranch . ProjectAndBranch (pure projectName)) - ) - . fst - ) + (SA.ProjectBranch . ProjectAndBranch (pure projectName) . fst) branches ) AmbiguousSwitch project (ProjectAndBranch currentProject branch) -> @@ -457,11 +451,9 @@ notifyNumbered = \case <> switch ["2"] <> " to pick one of these." ), - [ (Text.cons '/' . into @Text &&& SA.ProjectBranch . ProjectAndBranch Nothing) branch, - ( (into @Text . ProjectAndBranch project) - &&& (SA.ProjectBranch . ProjectAndBranch (pure project)) - ) - $ UnsafeProjectBranchName "main" + [ SA.ProjectBranch $ ProjectAndBranch Nothing branch, + SA.ProjectBranch . ProjectAndBranch (pure project) $ + UnsafeProjectBranchName "main" ] ) where @@ -490,8 +482,8 @@ notifyNumbered = \case <> reset (resetArgs ["2"]) <> " to pick one of these." ), - [ (Text.cons '/' . into @Text &&& SA.ProjectBranch . ProjectAndBranch Nothing) branch, - (into @Text . show &&& SA.AbsolutePath) absPath0 + [ SA.ProjectBranch $ ProjectAndBranch Nothing branch, + SA.AbsolutePath absPath0 ] ) where @@ -533,7 +525,7 @@ notifyNumbered = \case ) (1, (mempty, mempty)) & snd - & over (_2 . mapped) (Name.toText &&& SA.Name) + & over (_2 . mapped) SA.Name externalDepsTable :: Map LabeledDependency (Set Name) -> [(P.Pretty P.ColorText, P.Pretty P.ColorText)] externalDepsTable = ifoldMap $ \ld dependents -> [(prettyLD ld, prettyDependents dependents)] @@ -613,7 +605,7 @@ showListEdits patch ppe = let lhsTermName = PPE.termName ppe (Referent.Ref lhsRef) -- We use the shortHash of the lhs rather than its name for numbered args, -- since its name is likely to be "historical", and won't work if passed to a ucm command. - let lhsHash = (ShortHash.toText . Reference.toShortHash &&& SA.Ref) $ lhsRef + let lhsHash = SA.Ref lhsRef case termEdit of TermEdit.Deprecate -> do lift $ tell ([lhsHash], []) @@ -624,7 +616,7 @@ showListEdits patch ppe = TermEdit.Replace rhsRef _typing -> do n2 <- gets snd <* modify (second succ) let rhsTermName = PPE.termName ppe (Referent.Ref rhsRef) - lift $ tell ([lhsHash], [(HQ.toText &&& SA.HashQualified) rhsTermName]) + lift $ tell ([lhsHash], [SA.HashQualified rhsTermName]) pure ( showNum n1 <> (P.syntaxToColor . prettyHashQualified $ lhsTermName), "-> " <> showNum n2 <> (P.syntaxToColor . prettyHashQualified $ rhsTermName) @@ -638,7 +630,7 @@ showListEdits patch ppe = let lhsTypeName = PPE.typeName ppe lhsRef -- We use the shortHash of the lhs rather than its name for numbered args, -- since its name is likely to be "historical", and won't work if passed to a ucm command. - let lhsHash = (ShortHash.toText . Reference.toShortHash &&& SA.Ref) $ lhsRef + let lhsHash = SA.Ref lhsRef case typeEdit of TypeEdit.Deprecate -> do lift $ tell ([lhsHash], []) @@ -649,7 +641,7 @@ showListEdits patch ppe = TypeEdit.Replace rhsRef -> do n2 <- gets snd <* modify (second succ) let rhsTypeName = PPE.typeName ppe rhsRef - lift $ tell ([lhsHash], [(HQ.toText &&& SA.HashQualified) rhsTypeName]) + lift $ tell ([lhsHash], [SA.HashQualified rhsTypeName]) pure ( showNum n1 <> (P.syntaxToColor . prettyHashQualified $ lhsTypeName), "-> " <> showNum n2 <> (P.syntaxToColor . prettyHashQualified $ rhsTypeName) @@ -1663,7 +1655,7 @@ notifyUser dir = \case prettyNamespaceKey dest <> "is already up-to-date with" <> P.group (prettyNamespaceKey src <> ".") - DumpNumberedArgs args -> pure . P.numberedList $ fmap (P.text . fst) args + DumpNumberedArgs args -> pure . P.numberedList $ fmap (P.text . IP.formatStructuredArgument) args NoConflictsOrEdits -> pure (P.okCallout "No conflicts or edits in progress.") HelpMessage pat -> pure $ IP.showPatternHelp pat @@ -2729,7 +2721,7 @@ renderNameConflicts ppe conflictedNames = do P.lines <$> do for (Map.toList conflictedNames) $ \(name, hashes) -> do prettyConflicts <- for hashes \hash -> do - n <- addNumberedArg $ (HQ.toText &&& SA.HashQualified) hash + n <- addNumberedArg $ SA.HashQualified hash pure $ formatNum n <> (P.blue . P.syntaxToColor . prettyHashQualified $ hash) pure . P.wrap $ ( "The " @@ -2761,7 +2753,7 @@ renderEditConflicts ppe Patch {..} = do <> (fmap Right . Map.toList . R.toMultimap . R.filterManyDom $ _termEdits) numberedHQName :: HQ.HashQualified Name -> Numbered Pretty numberedHQName hqName = do - n <- addNumberedArg $ (HQ.toText &&& SA.HashQualified) hqName + n <- addNumberedArg $ SA.HashQualified hqName pure $ formatNum n <> styleHashQualified P.bold hqName formatTypeEdits :: (Reference, Set TypeEdit.TypeEdit) -> @@ -2800,9 +2792,9 @@ renderEditConflicts ppe Patch {..} = do Numbered Pretty formatConflict = either formatTypeEdits formatTermEdits -type Numbered = State.State (Int, Seq.Seq (Text, StructuredArgument)) +type Numbered = State.State (Int, Seq.Seq StructuredArgument) -addNumberedArg :: (Text, StructuredArgument) -> Numbered Int +addNumberedArg :: StructuredArgument -> Numbered Int addNumberedArg s = do (n, args) <- State.get State.put (n + 1, args Seq.|> s) @@ -2874,11 +2866,11 @@ todoOutput ppe todo = runNumbered do todoEdits :: Numbered Pretty todoEdits = do numberedTypes <- for (unscore <$> dirtyTypes) \(ref, displayObj) -> do - n <- addNumberedArg . (HQ.toText &&& SA.HashQualified) $ PPE.typeName ppeu ref + n <- addNumberedArg . SA.HashQualified $ PPE.typeName ppeu ref pure $ formatNum n <> prettyDeclPair ppeu (ref, displayObj) let filteredTerms = goodTerms (unscore <$> dirtyTerms) termNumbers <- for filteredTerms \(ref, _, _) -> do - n <- addNumberedArg . (HQ.toText &&& SA.HashQualified) $ PPE.termName ppeu ref + n <- addNumberedArg . SA.HashQualified $ PPE.termName ppeu ref pure $ formatNum n let formattedTerms = TypePrinter.prettySignaturesCT ppes filteredTerms numberedTerms = zipWith (<>) termNumbers formattedTerms @@ -3283,21 +3275,13 @@ showDiffNamespace sn ppe oldPath newPath OBD.BranchDiffOutput {..} = -- DeclPrinter.prettyDeclHeader : HQ -> Either numPatch :: Input.AbsBranchId -> Name -> Numbered Pretty numPatch prefix name = - addNumberedArg' $ (prefixBranchId prefix &&& SA.NameWithBranchPrefix prefix) name + addNumberedArg' $ SA.NameWithBranchPrefix prefix name numHQ' :: Input.AbsBranchId -> HQ'.HashQualified Name -> Referent -> Numbered Pretty numHQ' prefix hq r = - addNumberedArg' . (HQ'.toTextWith (prefixBranchId prefix) &&& SA.HashQualifiedWithBranchPrefix prefix) $ HQ'.requalify hq r - - -- E.g. - -- prefixBranchId "#abcdef" "base.List.map" -> "#abcdef:.base.List.map" - -- prefixBranchId ".base" "List.map" -> ".base.List.map" - prefixBranchId :: Input.AbsBranchId -> Name -> Text - prefixBranchId branchId name = case branchId of - Left sch -> "#" <> SCH.toText sch <> ":" <> Name.toText (Name.makeAbsolute name) - Right pathPrefix -> Name.toText (Name.makeAbsolute . Path.prefixName pathPrefix $ name) + addNumberedArg' . SA.HashQualifiedWithBranchPrefix prefix $ HQ'.requalify hq r - addNumberedArg' :: (Text, StructuredArgument) -> Numbered Pretty + addNumberedArg' :: StructuredArgument -> Numbered Pretty addNumberedArg' s = case sn of ShowNumbers -> do n <- addNumberedArg s @@ -3552,7 +3536,7 @@ numberedArgsForEndangerments (PPED.unsuffixifiedPPE -> ppe) m = m & Map.elems & concatMap toList - & fmap ((HQ.toText &&& SA.HashQualified) . PPE.labeledRefName ppe) + & fmap (SA.HashQualified . PPE.labeledRefName ppe) -- | Format and render all dependents which are endangered by references going extinct. endangeredDependentsTable :: From ff785cb8a5e304fb6ce784ceb97ec886c96e0534 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Wed, 22 May 2024 23:46:31 -0600 Subject: [PATCH 06/10] Allow structured args in `find` commands --- unison-cli/src/Unison/CommandLine/InputPatterns.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index 91bf1960f2a..97c41767f55 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -1355,7 +1355,7 @@ find' cmd fscope = I.Visible [("query", ZeroPlus, exactDefinitionArg)] findHelp - (fmap (Input.FindI False fscope) . traverse (unsupportedStructuredArgument "text")) + (pure . Input.FindI False fscope . fmap unifyArgument) findShallow :: InputPattern findShallow = From 647072d2c7250952932b28aa5a421327267093dd Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 28 May 2024 14:34:05 -0600 Subject: [PATCH 07/10] Allow structured args in more find commands --- .../src/Unison/CommandLine/InputPatterns.hs | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index b674b8c4a00..8a01919f4ba 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -1244,17 +1244,8 @@ findIn' cmd mkfscope = I.Visible [("namespace", Required, namespaceArg), ("query", ZeroPlus, exactDefinitionArg)] findHelp - $ \case - p : args -> - Input.FindI False . mkfscope - <$> first P.text (handlePathArg p) - -- __FIXME__: This changes things a bit. Previously, `find` and - -- friends would just expand the numbered args and search - -- for them like any other string, but now it recognizes - -- that you’re trying to look up something you already - -- have, and refuses to. Is that the right thing to do? We - -- _could_ still serialize in this case. - <*> traverse (unsupportedStructuredArgument "text") args + \case + p : args -> Input.FindI False . mkfscope <$> handlePathArg p <*> pure (unifyArgument <$> args) _ -> Left findHelp findHelp :: P.Pretty CT.ColorText @@ -1332,9 +1323,7 @@ findVerbose = ( "`find.verbose` searches for definitions like `find`, but includes hashes " <> "and aliases in the results." ) - ( fmap (Input.FindI True $ Input.FindLocal Path.empty) - . traverse (unsupportedStructuredArgument "text") - ) + (pure . Input.FindI True (Input.FindLocal Path.empty) . fmap unifyArgument) findVerboseAll :: InputPattern findVerboseAll = @@ -1346,9 +1335,7 @@ findVerboseAll = ( "`find.all.verbose` searches for definitions like `find.all`, but includes hashes " <> "and aliases in the results." ) - ( fmap (Input.FindI True $ Input.FindLocalAndDeps Path.empty) - . traverse (unsupportedStructuredArgument "text") - ) + (pure . Input.FindI True (Input.FindLocalAndDeps Path.empty) . fmap unifyArgument) renameTerm :: InputPattern renameTerm = From c6f1f2c2a8625c4d34d6a43e85bf691c574bedaa Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 28 May 2024 14:37:26 -0600 Subject: [PATCH 08/10] =?UTF-8?q?Don=E2=80=99t=20allow=20`ProjectBranch`?= =?UTF-8?q?=20as=20project=20arg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unison-cli/src/Unison/CommandLine/InputPatterns.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index 8a01919f4ba..8fa5afb7698 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -369,8 +369,6 @@ handleProjectArg = ) ( \case SA.Project project -> pure project - -- __FIXME__: Do we want to treat a project branch as a project? - SA.ProjectBranch (ProjectAndBranch (Just project) _) -> pure project otherArgType -> Left $ wrongStructuredArgument "a path" otherArgType ) From a9c29d01c5f05203f5e0e55e1b253a4e5890bb7e Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 28 May 2024 14:35:31 -0600 Subject: [PATCH 09/10] Have handlers fail with `Pretty`, not `Text` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This also generally improves formatting: - follows the longer line convention in Unison and - removes unnecessary `( … )` and `$` before `LambdaCase` args. --- .../src/Unison/CommandLine/InputPatterns.hs | 925 +++++++----------- 1 file changed, 336 insertions(+), 589 deletions(-) diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index 8fa5afb7698..7f7524dd2ba 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -233,24 +233,15 @@ formatStructuredArgument = \case SA.HashQualified hqName -> HQ.toText hqName SA.Project projectName -> into @Text projectName SA.ProjectBranch (ProjectAndBranch mproj branch) -> - maybe - (Text.cons '/' . into @Text) - (\project -> into @Text . ProjectAndBranch project) - mproj - branch - SA.Ref reference -> - -- also: ShortHash.toText . Reference.toShortHash - Reference.toText reference - SA.Namespace causalHash -> - -- also: ("#" <>) . Hash.toBase32HexText . unCausalHash - ("#" <>) . SCH.toText $ SCH.fromHash schLength causalHash - SA.NameWithBranchPrefix absBranchId name -> - prefixBranchId absBranchId name - SA.HashQualifiedWithBranchPrefix absBranchId hq'Name -> - HQ'.toTextWith (prefixBranchId absBranchId) hq'Name + maybe (Text.cons '/' . into @Text) (\project -> into @Text . ProjectAndBranch project) mproj branch + -- also: ShortHash.toText . Reference.toShortHash + SA.Ref reference -> Reference.toText reference + -- also: ("#" <>) . Hash.toBase32HexText . unCausalHash + SA.Namespace causalHash -> ("#" <>) . SCH.toText $ SCH.fromHash schLength causalHash + SA.NameWithBranchPrefix absBranchId name -> prefixBranchId absBranchId name + SA.HashQualifiedWithBranchPrefix absBranchId hq'Name -> HQ'.toTextWith (prefixBranchId absBranchId) hq'Name SA.ShallowListEntry path entry -> entryToHQText path entry - SA.SearchResult searchRoot searchResult -> - HQ.toText $ searchResultToHQ searchRoot searchResult + SA.SearchResult searchRoot searchResult -> HQ.toText $ searchResultToHQ searchRoot searchResult where -- E.g. -- prefixBranchId "#abcdef" "base.List.map" -> "#abcdef:.base.List.map" @@ -261,8 +252,8 @@ formatStructuredArgument = \case Right pathPrefix -> Name.toText (Name.makeAbsolute . Path.prefixName pathPrefix $ name) entryToHQText :: Path' -> ShallowListEntry v Ann -> Text - entryToHQText pathArg e = - fixup $ case e of + entryToHQText pathArg = + fixup . \case ShallowTypeEntry te -> Backend.typeEntryDisplayName te ShallowTermEntry te -> Backend.termEntryDisplayName te ShallowBranchEntry ns _ _ -> NameSegment.toEscapedText ns @@ -303,26 +294,17 @@ searchResultToHQ oprefix = \case addPrefix :: Name -> Name addPrefix = maybe id Path.prefixName2 oprefix -unsupportedStructuredArgument :: - Text -> I.Argument -> Either (P.Pretty CT.ColorText) String +unsupportedStructuredArgument :: Text -> I.Argument -> Either (P.Pretty CT.ColorText) String unsupportedStructuredArgument expected = - either - pure - (const . Left . P.text $ "can’t use a numbered argument for " <> expected) + either pure (const . Left . P.text $ "can’t use a numbered argument for " <> expected) expectedButActually :: Text -> Text -> Text -> Text expectedButActually expected actualValue actualType = - "Expected " - <> expected - <> ", but the numbered arg resulted in " - <> actualValue - <> ", which is " - <> actualType - <> "." - -wrongStructuredArgument :: Text -> StructuredArgument -> Text + "Expected " <> expected <> ", but the numbered arg resulted in " <> actualValue <> ", which is " <> actualType <> "." + +wrongStructuredArgument :: Text -> StructuredArgument -> P.Pretty CT.ColorText wrongStructuredArgument expected actual = - expectedButActually + P.text $ expectedButActually expected (formatStructuredArgument actual) case actual of @@ -358,194 +340,151 @@ makeExampleEOS p args = helpFor :: InputPattern -> P.Pretty CT.ColorText helpFor = I.help -handleProjectArg :: I.Argument -> Either Text ProjectName +handleProjectArg :: I.Argument -> Either (P.Pretty CT.ColorText) ProjectName handleProjectArg = either ( \name -> - first - (const $ "“" <> Text.pack name <> "” is an invalid project name") - . tryInto @ProjectName - $ Text.pack name - ) - ( \case - SA.Project project -> pure project - otherArgType -> Left $ wrongStructuredArgument "a path" otherArgType + first (const . P.text $ "“" <> Text.pack name <> "” is an invalid project name") . tryInto @ProjectName $ + Text.pack name ) + \case + SA.Project project -> pure project + otherArgType -> Left $ wrongStructuredArgument "a project" otherArgType -handleLooseCodeOrProjectArg :: - I.Argument -> Either Text Input.LooseCodeOrProject +handleLooseCodeOrProjectArg :: I.Argument -> Either (P.Pretty CT.ColorText) Input.LooseCodeOrProject handleLooseCodeOrProjectArg = either - ( maybe (Left "invalid path or project branch") pure - . parseLooseCodeOrProject - ) - ( \case - SA.AbsolutePath path -> pure . This $ Path.absoluteToPath' path - SA.ProjectBranch pb -> pure $ That pb - otherArgType -> - Left $ wrongStructuredArgument "a path or project branch" otherArgType - ) + (maybe (Left $ P.text "invalid path or project branch") pure . parseLooseCodeOrProject) + \case + SA.AbsolutePath path -> pure . This $ Path.absoluteToPath' path + SA.ProjectBranch pb -> pure $ That pb + otherArgType -> Left $ wrongStructuredArgument "a path or project branch" otherArgType handleMaybeProjectBranchArg :: - I.Argument -> - Either (P.Pretty CT.ColorText) (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) + I.Argument -> Either (P.Pretty CT.ColorText) (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) handleMaybeProjectBranchArg = either (megaparse branchWithOptionalProjectParser . Text.pack) - ( \case - SA.ProjectBranch pb -> pure pb - otherArgType -> Left . P.text $ wrongStructuredArgument "a branch" otherArgType - ) + \case + SA.ProjectBranch pb -> pure pb + otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType handleProjectMaybeBranchArg :: - I.Argument -> - Either Text (ProjectAndBranch ProjectName (Maybe ProjectBranchNameOrLatestRelease)) + I.Argument -> Either (P.Pretty CT.ColorText) (ProjectAndBranch ProjectName (Maybe ProjectBranchNameOrLatestRelease)) handleProjectMaybeBranchArg = either - (first (const "The argument wasn’t a project") . tryInto . Text.pack) - ( \case - SA.Project proj -> pure $ ProjectAndBranch proj Nothing - SA.ProjectBranch (ProjectAndBranch (Just proj) branch) -> - pure . ProjectAndBranch proj . pure $ ProjectBranchNameOrLatestRelease'Name branch - otherArgType -> Left $ wrongStructuredArgument "a project" otherArgType - ) + (first (const $ P.text "The argument wasn’t a project") . tryInto . Text.pack) + \case + SA.Project proj -> pure $ ProjectAndBranch proj Nothing + SA.ProjectBranch (ProjectAndBranch (Just proj) branch) -> + pure . ProjectAndBranch proj . pure $ ProjectBranchNameOrLatestRelease'Name branch + otherArgType -> Left $ wrongStructuredArgument "a project" otherArgType -handleHashQualifiedNameArg :: - I.Argument -> Either (P.Pretty CT.ColorText) (HQ.HashQualified Name) +handleHashQualifiedNameArg :: I.Argument -> Either (P.Pretty CT.ColorText) (HQ.HashQualified Name) handleHashQualifiedNameArg = either parseHashQualifiedName - ( \case - SA.Name name -> pure $ HQ.NameOnly name - SA.NameWithBranchPrefix (Left _) name -> pure $ HQ.NameOnly name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . HQ.NameOnly . Name.makeAbsolute $ Path.prefixName prefix name - SA.Ref ref -> pure . HQ.HashOnly $ Reference.toShortHash ref - SA.HashQualified hqname -> pure hqname - SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ HQ'.toHQ hqname - SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> - pure . HQ'.toHQ $ Name.makeAbsolute . Path.prefixName prefix <$> hqname - SA.SearchResult mpath result -> pure $ searchResultToHQ mpath result - otherArgType -> - Left . P.text $ wrongStructuredArgument "a hash-qualified name" otherArgType - ) - -handlePathArg :: I.Argument -> Either Text Path.Path + \case + SA.Name name -> pure $ HQ.NameOnly name + SA.NameWithBranchPrefix mprefix name -> + pure . HQ.NameOnly $ foldr (\prefix -> Name.makeAbsolute . Path.prefixName prefix) name mprefix + SA.Ref ref -> pure . HQ.HashOnly $ Reference.toShortHash ref + SA.HashQualified hqname -> pure hqname + SA.HashQualifiedWithBranchPrefix mprefix hqname -> + pure . HQ'.toHQ $ foldr (\prefix -> fmap $ Name.makeAbsolute . Path.prefixName prefix) hqname mprefix + SA.SearchResult mpath result -> pure $ searchResultToHQ mpath result + otherArgType -> Left $ wrongStructuredArgument "a hash-qualified name" otherArgType + +handlePathArg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Path handlePathArg = either - Path.parsePath + (first P.text . Path.parsePath) \case SA.Name name -> pure $ Path.fromName name - SA.NameWithBranchPrefix (Left _) name -> pure $ Path.fromName name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . Path.fromName $ Path.prefixName prefix name - otherArgType -> - Left $ wrongStructuredArgument "a relative path" otherArgType + SA.NameWithBranchPrefix mprefix name -> pure . Path.fromName $ foldr Path.prefixName name mprefix + otherArgType -> Left $ wrongStructuredArgument "a relative path" otherArgType -handlePath'Arg :: I.Argument -> Either Text Path.Path' +handlePath'Arg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Path' handlePath'Arg = either - Path.parsePath' - ( \case - SA.AbsolutePath path -> pure $ Path.absoluteToPath' path - SA.Name name -> pure $ Path.fromName' name - SA.NameWithBranchPrefix (Left _) name -> pure $ Path.fromName' name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name - otherArgType -> Left $ wrongStructuredArgument "a namespace" otherArgType - ) + (first P.text . Path.parsePath') + \case + SA.AbsolutePath path -> pure $ Path.absoluteToPath' path + SA.Name name -> pure $ Path.fromName' name + SA.NameWithBranchPrefix mprefix name -> + pure . Path.fromName' $ foldr (\prefix -> Name.makeAbsolute . Path.prefixName prefix) name mprefix + otherArgType -> Left $ wrongStructuredArgument "a namespace" otherArgType -handleNewName :: I.Argument -> Either Text Path.Split' +handleNewName :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Split' handleNewName = either - Path.parseSplit' + (first P.text . Path.parseSplit') (const . Left $ "can’t use a numbered argument for a new name") -handleNewPath :: I.Argument -> Either Text Path.Path' +handleNewPath :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Path' handleNewPath = either - Path.parsePath' + (first P.text . Path.parsePath') (const . Left $ "can’t use a numbered argument for a new namespace") -- | When only a relative name is allowed. -handleSplitArg :: I.Argument -> Either Text Path.Split +handleSplitArg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Split handleSplitArg = either - Path.parseSplit - ( \case - SA.Name name | Name.isRelative name -> pure $ Path.splitFromName name - SA.NameWithBranchPrefix (Left _) name - | Name.isRelative name -> - pure $ Path.splitFromName name - SA.NameWithBranchPrefix (Right prefix) name - | Name.isRelative name -> - pure . Path.splitFromName . Name.makeAbsolute $ - Path.prefixName prefix name - otherNumArg -> - Left $ wrongStructuredArgument "a relative name" otherNumArg - ) + (first P.text . Path.parseSplit) + \case + SA.Name name | Name.isRelative name -> pure $ Path.splitFromName name + SA.NameWithBranchPrefix (Left _) name | Name.isRelative name -> pure $ Path.splitFromName name + SA.NameWithBranchPrefix (Right prefix) name + | Name.isRelative name -> pure . Path.splitFromName . Name.makeAbsolute $ Path.prefixName prefix name + otherNumArg -> Left $ wrongStructuredArgument "a relative name" otherNumArg -handleSplit'Arg :: I.Argument -> Either Text Path.Split' +handleSplit'Arg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Split' handleSplit'Arg = either - Path.parseSplit' - ( \case - SA.Name name -> pure $ Path.splitFromName' name - SA.NameWithBranchPrefix (Left _) name -> pure $ Path.splitFromName' name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . Path.splitFromName' . Name.makeAbsolute $ Path.prefixName prefix name - otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg - ) + (first P.text . Path.parseSplit') + \case + SA.Name name -> pure $ Path.splitFromName' name + SA.NameWithBranchPrefix (Left _) name -> pure $ Path.splitFromName' name + SA.NameWithBranchPrefix (Right prefix) name -> + pure . Path.splitFromName' . Name.makeAbsolute $ Path.prefixName prefix name + otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg -handleProjectBranchNameArg :: I.Argument -> Either Text ProjectBranchName +handleProjectBranchNameArg :: I.Argument -> Either (P.Pretty CT.ColorText) ProjectBranchName handleProjectBranchNameArg = either - (first (const "Wanted a branch name, but it wasn’t") . tryInto . Text.pack) - ( \case - SA.ProjectBranch (ProjectAndBranch _ branch) -> pure branch - otherNumArg -> Left $ wrongStructuredArgument "a branch name" otherNumArg - ) + (first (const $ P.text "Wanted a branch name, but it wasn’t") . tryInto . Text.pack) + \case + SA.ProjectBranch (ProjectAndBranch _ branch) -> pure branch + otherNumArg -> Left $ wrongStructuredArgument "a branch name" otherNumArg -handleBranchIdArg :: I.Argument -> Either Text Input.BranchId +handleBranchIdArg :: I.Argument -> Either (P.Pretty CT.ColorText) Input.BranchId handleBranchIdArg = either - Input.parseBranchId - ( \case - SA.AbsolutePath path -> pure . pure $ Path.absoluteToPath' path - SA.Name name -> pure . pure $ Path.fromName' name - SA.NameWithBranchPrefix mprefix name -> - pure . pure . Path.fromName' $ - either (const name) (Name.makeAbsolute . flip Path.prefixName name) mprefix - SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash - otherNumArg -> Left $ wrongStructuredArgument "a branch id" otherNumArg - ) + (first P.text . Input.parseBranchId) + \case + SA.AbsolutePath path -> pure . pure $ Path.absoluteToPath' path + SA.Name name -> pure . pure $ Path.fromName' name + SA.NameWithBranchPrefix mprefix name -> + pure . pure . Path.fromName' $ either (const name) (Name.makeAbsolute . flip Path.prefixName name) mprefix + SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash + otherNumArg -> Left $ wrongStructuredArgument "a branch id" otherNumArg handleBranchIdOrProjectArg :: I.Argument -> - Either - Text - ( These - Input.BranchId - (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) - ) + Either (P.Pretty CT.ColorText) (These Input.BranchId (ProjectAndBranch (Maybe ProjectName) ProjectBranchName)) handleBranchIdOrProjectArg = either - ( maybe (Left "Expected a branch or project, but it’s not") pure - . branchIdOrProject - ) - ( \case - SA.Namespace hash -> pure . This . Left $ SCH.fromHash schLength hash - SA.AbsolutePath path -> - pure . This . pure $ Path.absoluteToPath' path - SA.Name name -> pure . This . pure $ Path.fromName' name - SA.NameWithBranchPrefix (Left _) name -> - pure . This . pure $ Path.fromName' name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . This . pure . Path.fromName' . Name.makeAbsolute $ - Path.prefixName prefix name - SA.ProjectBranch pb -> pure $ pure pb - otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType - ) + (maybe (Left $ P.text "Expected a branch or project, but it’s not") pure . branchIdOrProject) + \case + SA.Namespace hash -> pure . This . Left $ SCH.fromHash schLength hash + SA.AbsolutePath path -> pure . This . pure $ Path.absoluteToPath' path + SA.Name name -> pure . This . pure $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . This . pure $ Path.fromName' name + SA.NameWithBranchPrefix (Right prefix) name -> + pure . This . pure . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name + SA.ProjectBranch pb -> pure $ pure pb + otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType where branchIdOrProject :: String -> @@ -565,57 +504,44 @@ handleBranchIdOrProjectArg = (Right bid, Left _) -> Just (This bid) (Right bid, Right pr) -> Just (These bid pr) -handleBranchId2Arg :: - I.Argument -> Either (P.Pretty P.ColorText) (Either ShortCausalHash BranchRelativePath) +handleBranchId2Arg :: I.Argument -> Either (P.Pretty P.ColorText) (Either ShortCausalHash BranchRelativePath) handleBranchId2Arg = either Input.parseBranchId2 - ( \case - SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash - SA.AbsolutePath path -> - pure . pure . LoosePath $ Path.absoluteToPath' path - SA.Name name -> pure . pure . LoosePath $ Path.fromName' name - SA.NameWithBranchPrefix (Left _) name -> - pure . pure . LoosePath $ Path.fromName' name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . pure . LoosePath . Path.fromName' . Name.makeAbsolute $ - Path.prefixName prefix name - SA.ProjectBranch (ProjectAndBranch mproject branch) -> - pure . pure . BranchRelative . This $ - maybe (Left branch) (pure . (,branch)) mproject - otherNumArg -> - Left . P.text $ wrongStructuredArgument "a branch id" otherNumArg - ) + \case + SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash + SA.AbsolutePath path -> pure . pure . LoosePath $ Path.absoluteToPath' path + SA.Name name -> pure . pure . LoosePath $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . pure . LoosePath $ Path.fromName' name + SA.NameWithBranchPrefix (Right prefix) name -> + pure . pure . LoosePath . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name + SA.ProjectBranch (ProjectAndBranch mproject branch) -> + pure . pure . BranchRelative . This $ maybe (Left branch) (pure . (,branch)) mproject + otherNumArg -> Left $ wrongStructuredArgument "a branch id" otherNumArg -handleBranchRelativePathArg :: - I.Argument -> Either (P.Pretty P.ColorText) BranchRelativePath +handleBranchRelativePathArg :: I.Argument -> Either (P.Pretty P.ColorText) BranchRelativePath handleBranchRelativePathArg = either parseBranchRelativePath - ( \case - SA.AbsolutePath path -> pure . LoosePath $ Path.absoluteToPath' path - SA.Name name -> pure . LoosePath $ Path.fromName' name - SA.NameWithBranchPrefix (Left _) name -> - pure . LoosePath $ Path.fromName' name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . LoosePath . Path.fromName' . Name.makeAbsolute $ - Path.prefixName prefix name - SA.ProjectBranch (ProjectAndBranch mproject branch) -> - pure . BranchRelative . This $ - maybe (Left branch) (pure . (,branch)) mproject - otherNumArg -> - Left . P.text $ wrongStructuredArgument "a branch id" otherNumArg - ) + \case + SA.AbsolutePath path -> pure . LoosePath $ Path.absoluteToPath' path + SA.Name name -> pure . LoosePath $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . LoosePath $ Path.fromName' name + SA.NameWithBranchPrefix (Right prefix) name -> + pure . LoosePath . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name + SA.ProjectBranch (ProjectAndBranch mproject branch) -> + pure . BranchRelative . This $ maybe (Left branch) (pure . (,branch)) mproject + otherNumArg -> Left $ wrongStructuredArgument "a branch id" otherNumArg -hqNameToSplit' :: HQ.HashQualified Name -> Either Text Path.HQSplit' +hqNameToSplit' :: HQ.HashQualified Name -> Either (P.Pretty CT.ColorText) Path.HQSplit' hqNameToSplit' = \case - HQ.HashOnly _ -> Left "Only have a hash" + HQ.HashOnly _ -> Left $ P.text "Only have a hash" HQ.NameOnly name -> pure . fmap HQ'.NameOnly $ Path.splitFromName' name HQ.HashQualified name hash -> pure . fmap (`HQ'.HashQualified` hash) $ Path.splitFromName' name -hqNameToSplit :: HQ.HashQualified Name -> Either Text Path.HQSplit +hqNameToSplit :: HQ.HashQualified Name -> Either (P.Pretty CT.ColorText) Path.HQSplit hqNameToSplit = \case - HQ.HashOnly _ -> Left "Only have a hash" + HQ.HashOnly _ -> Left $ P.text "Only have a hash" HQ.NameOnly name -> pure . fmap HQ'.NameOnly $ Path.splitFromName name HQ.HashQualified name hash -> pure . fmap (`HQ'.HashQualified` hash) $ Path.splitFromName name @@ -629,85 +555,75 @@ hq'NameToSplit = \case HQ'.NameOnly name -> HQ'.NameOnly <$> Path.splitFromName name HQ'.HashQualified name hash -> flip HQ'.HashQualified hash <$> Path.splitFromName name -handleHashQualifiedSplit'Arg :: I.Argument -> Either Text Path.HQSplit' +handleHashQualifiedSplit'Arg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.HQSplit' handleHashQualifiedSplit'Arg = either - Path.parseHQSplit' - ( \case - SA.HashQualified name -> hqNameToSplit' name - SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ hq'NameToSplit' hqname - SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> - pure . hq'NameToSplit' $ Name.makeAbsolute . Path.prefixName prefix <$> hqname - SA.SearchResult mpath result -> hqNameToSplit' $ searchResultToHQ mpath result - otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg - ) - -handleHashQualifiedSplitArg :: I.Argument -> Either Text Path.HQSplit + (first P.text . Path.parseHQSplit') + \case + SA.HashQualified name -> hqNameToSplit' name + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ hq'NameToSplit' hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> + pure . hq'NameToSplit' $ Name.makeAbsolute . Path.prefixName prefix <$> hqname + SA.SearchResult mpath result -> hqNameToSplit' $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg + +handleHashQualifiedSplitArg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.HQSplit handleHashQualifiedSplitArg = either - Path.parseHQSplit - ( \case - SA.HashQualified name -> hqNameToSplit name - SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ hq'NameToSplit hqname - SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> - pure . hq'NameToSplit $ Name.makeAbsolute . Path.prefixName prefix <$> hqname - SA.SearchResult mpath result -> hqNameToSplit $ searchResultToHQ mpath result - otherNumArg -> Left $ wrongStructuredArgument "a relative name" otherNumArg - ) - -handleShortCausalHashArg :: I.Argument -> Either Text ShortCausalHash + (first P.text . Path.parseHQSplit) + \case + SA.HashQualified name -> hqNameToSplit name + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ hq'NameToSplit hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> + pure . hq'NameToSplit $ Name.makeAbsolute . Path.prefixName prefix <$> hqname + SA.SearchResult mpath result -> hqNameToSplit $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a relative name" otherNumArg + +handleShortCausalHashArg :: I.Argument -> Either (P.Pretty CT.ColorText) ShortCausalHash handleShortCausalHashArg = either - (first Text.pack . Input.parseShortCausalHash) - ( \case - SA.Namespace hash -> pure $ SCH.fromHash schLength hash - otherNumArg -> Left $ wrongStructuredArgument "a causal hash" otherNumArg - ) + (first (P.text . Text.pack) . Input.parseShortCausalHash) + \case + SA.Namespace hash -> pure $ SCH.fromHash schLength hash + otherNumArg -> Left $ wrongStructuredArgument "a causal hash" otherNumArg handleShortHashOrHQSplit'Arg :: - I.Argument -> Either Text (Either ShortHash Path.HQSplit') + I.Argument -> Either (P.Pretty CT.ColorText) (Either ShortHash Path.HQSplit') handleShortHashOrHQSplit'Arg = either - Path.parseShortHashOrHQSplit' - ( \case - SA.Ref ref -> pure $ Left $ Reference.toShortHash ref - SA.HashQualified name -> pure <$> hqNameToSplit' name - SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure . pure $ hq'NameToSplit' hqname - SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> - pure . pure $ hq'NameToSplit' (Name.makeAbsolute . Path.prefixName prefix <$> hqname) - SA.SearchResult mpath result -> - fmap pure . hqNameToSplit' $ searchResultToHQ mpath result - otherNumArg -> Left $ wrongStructuredArgument "a hash or name" otherNumArg - ) - -handleRelativeNameSegmentArg :: I.Argument -> Either Text NameSegment + (first P.text . Path.parseShortHashOrHQSplit') + \case + SA.Ref ref -> pure $ Left $ Reference.toShortHash ref + SA.HashQualified name -> pure <$> hqNameToSplit' name + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure . pure $ hq'NameToSplit' hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> + pure . pure $ hq'NameToSplit' (Name.makeAbsolute . Path.prefixName prefix <$> hqname) + SA.SearchResult mpath result -> fmap pure . hqNameToSplit' $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a hash or name" otherNumArg + +handleRelativeNameSegmentArg :: I.Argument -> Either (P.Pretty CT.ColorText) NameSegment handleRelativeNameSegmentArg arg = do name <- handleNameArg arg let (segment NE.:| tail) = Name.reverseSegments name if Name.isRelative name && null tail then pure segment - else Left "Wanted a single relative name segment, but it wasn’t." + else Left $ P.text "Wanted a single relative name segment, but it wasn’t." -handleNameArg :: I.Argument -> Either Text Name +handleNameArg :: I.Argument -> Either (P.Pretty CT.ColorText) Name handleNameArg = either - (Name.parseTextEither . Text.pack) - ( \case - SA.Name name -> pure name - SA.NameWithBranchPrefix (Left _) name -> pure name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . Name.makeAbsolute $ Path.prefixName prefix name - SA.HashQualified hqname -> - maybe (Left "can’t find a name from the numbered arg") pure $ HQ.toName hqname - SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ HQ'.toName hqname - SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> - pure . Name.makeAbsolute . Path.prefixName prefix $ HQ'.toName hqname - SA.SearchResult mpath result -> - maybe (Left "can’t find a name from the numbered arg") pure - . HQ.toName - $ searchResultToHQ mpath result - otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg - ) + (first P.text . Name.parseTextEither . Text.pack) + \case + SA.Name name -> pure name + SA.NameWithBranchPrefix (Left _) name -> pure name + SA.NameWithBranchPrefix (Right prefix) name -> pure . Name.makeAbsolute $ Path.prefixName prefix name + SA.HashQualified hqname -> maybe (Left "can’t find a name from the numbered arg") pure $ HQ.toName hqname + SA.HashQualifiedWithBranchPrefix (Left _) hqname -> pure $ HQ'.toName hqname + SA.HashQualifiedWithBranchPrefix (Right prefix) hqname -> + pure . Name.makeAbsolute . Path.prefixName prefix $ HQ'.toName hqname + SA.SearchResult mpath result -> + maybe (Left "can’t find a name from the numbered arg") pure . HQ.toName $ searchResultToHQ mpath result + otherNumArg -> Left $ wrongStructuredArgument "a name" otherNumArg handlePullSourceArg :: I.Argument -> @@ -717,68 +633,45 @@ handlePullSourceArg :: handlePullSourceArg = either (megaparse (readRemoteNamespaceParser ProjectBranchSpecifier'NameOrLatestRelease) . Text.pack) - ( \case - SA.Project project -> - pure . RemoteRepo.ReadShare'ProjectBranch $ This project - SA.ProjectBranch (ProjectAndBranch project branch) -> - pure . RemoteRepo.ReadShare'ProjectBranch . maybe That These project $ - ProjectBranchNameOrLatestRelease'Name branch - otherNumArg -> - Left . P.text $ wrongStructuredArgument "a source to pull from" otherNumArg - ) + \case + SA.Project project -> pure . RemoteRepo.ReadShare'ProjectBranch $ This project + SA.ProjectBranch (ProjectAndBranch project branch) -> + pure . RemoteRepo.ReadShare'ProjectBranch . maybe That These project $ + ProjectBranchNameOrLatestRelease'Name branch + otherNumArg -> Left $ wrongStructuredArgument "a source to pull from" otherNumArg handlePushTargetArg :: - I.Argument -> - Either Text (WriteRemoteNamespace (These ProjectName ProjectBranchName)) + I.Argument -> Either (P.Pretty CT.ColorText) (WriteRemoteNamespace (These ProjectName ProjectBranchName)) handlePushTargetArg = either - ( maybe (Left "Wanted a source to push from, but this ain’t it.") pure - . parsePushTarget - ) - ( fmap RemoteRepo.WriteRemoteProjectBranch - . \case - SA.Project project -> pure $ This project - SA.ProjectBranch (ProjectAndBranch project branch) -> - pure $ maybe That These project branch - otherNumArg -> - Left $ wrongStructuredArgument "a source to push from" otherNumArg - ) + (maybe (Left "Wanted a source to push from, but this ain’t it.") pure . parsePushTarget) + $ fmap RemoteRepo.WriteRemoteProjectBranch . \case + SA.Project project -> pure $ This project + SA.ProjectBranch (ProjectAndBranch project branch) -> pure $ maybe That These project branch + otherNumArg -> Left $ wrongStructuredArgument "a source to push from" otherNumArg -handlePushSourceArg :: I.Argument -> Either Text Input.PushSource +handlePushSourceArg :: I.Argument -> Either (P.Pretty CT.ColorText) Input.PushSource handlePushSourceArg = either - ( maybe (Left "Wanted a source to push from, but this ain’t it.") pure - . parsePushSource - ) - ( \case - SA.AbsolutePath path -> pure . Input.PathySource $ Path.absoluteToPath' path - SA.Name name -> pure . Input.PathySource $ Path.fromName' name - SA.NameWithBranchPrefix (Left _) name -> - pure . Input.PathySource $ Path.fromName' name - SA.NameWithBranchPrefix (Right prefix) name -> - pure . Input.PathySource . Path.fromName' . Name.makeAbsolute $ - Path.prefixName prefix name - SA.Project project -> pure . Input.ProjySource $ This project - SA.ProjectBranch (ProjectAndBranch project branch) -> - pure . Input.ProjySource . maybe That These project $ branch - otherNumArg -> - Left $ wrongStructuredArgument "a source to push from" otherNumArg - ) + (maybe (Left $ P.text "Wanted a source to push from, but this ain’t it.") pure . parsePushSource) + \case + SA.AbsolutePath path -> pure . Input.PathySource $ Path.absoluteToPath' path + SA.Name name -> pure . Input.PathySource $ Path.fromName' name + SA.NameWithBranchPrefix (Left _) name -> pure . Input.PathySource $ Path.fromName' name + SA.NameWithBranchPrefix (Right prefix) name -> + pure . Input.PathySource . Path.fromName' . Name.makeAbsolute $ Path.prefixName prefix name + SA.Project project -> pure . Input.ProjySource $ This project + SA.ProjectBranch (ProjectAndBranch project branch) -> pure . Input.ProjySource $ maybe That These project branch + otherNumArg -> Left $ wrongStructuredArgument "a source to push from" otherNumArg -handleProjectAndBranchNamesArg :: I.Argument -> Either Text ProjectAndBranchNames +handleProjectAndBranchNamesArg :: I.Argument -> Either (P.Pretty CT.ColorText) ProjectAndBranchNames handleProjectAndBranchNamesArg = either - ( first (const "The argument wasn’t a project or branch") - . tryInto @ProjectAndBranchNames - . Text.pack - ) - ( fmap ProjectAndBranchNames'Unambiguous . \case - SA.Project project -> pure $ This project - SA.ProjectBranch (ProjectAndBranch mproj branch) -> - pure $ maybe That These mproj branch - otherNumArg -> - Left $ wrongStructuredArgument "a project or branch" otherNumArg - ) + (first (const $ P.text "The argument wasn’t a project or branch") . tryInto @ProjectAndBranchNames . Text.pack) + $ fmap ProjectAndBranchNames'Unambiguous . \case + SA.Project project -> pure $ This project + SA.ProjectBranch (ProjectAndBranch mproj branch) -> pure $ maybe That These mproj branch + otherNumArg -> Left $ wrongStructuredArgument "a project or branch" otherNumArg mergeBuiltins :: InputPattern mergeBuiltins = @@ -788,9 +681,9 @@ mergeBuiltins = I.Hidden [("namespace", Optional, namespaceArg)] "Adds the builtins (excluding `io` and misc) to the specified namespace. Defaults to `builtin.`" - $ \case + \case [] -> pure . Input.MergeBuiltinsI $ Nothing - [p] -> bimap P.text (Input.MergeBuiltinsI . Just) $ handlePathArg p + [p] -> Input.MergeBuiltinsI . Just <$> handlePathArg p _ -> Left (I.help mergeBuiltins) mergeIOBuiltins :: InputPattern @@ -803,7 +696,7 @@ mergeIOBuiltins = "Adds all the builtins, including `io` and misc., to the specified namespace. Defaults to `builtin.`" \case [] -> pure . Input.MergeIOBuiltinsI $ Nothing - [p] -> bimap P.text (Input.MergeIOBuiltinsI . Just) $ handlePathArg p + [p] -> Input.MergeIOBuiltinsI . Just <$> handlePathArg p _ -> Left (I.help mergeBuiltins) updateBuiltins :: InputPattern @@ -839,16 +732,15 @@ todo = ) ] ) - ( \case - patchStr : ws -> first (warn . P.text) $ do - patch <- handleSplit'Arg patchStr - branch <- case ws of - [] -> pure Path.relativeEmpty' - [pathStr] -> handlePath'Arg pathStr - _ -> Left "`todo` just takes a patch and one optional namespace" - Right $ Input.TodoI (Just patch) branch - [] -> Right $ Input.TodoI Nothing Path.relativeEmpty' - ) + \case + patchStr : ws -> first warn $ do + patch <- handleSplit'Arg patchStr + branch <- case ws of + [] -> pure Path.relativeEmpty' + [pathStr] -> handlePath'Arg pathStr + _ -> Left "`todo` just takes a patch and one optional namespace" + Right $ Input.TodoI (Just patch) branch + [] -> Right $ Input.TodoI Nothing Path.relativeEmpty' load :: InputPattern load = @@ -866,13 +758,10 @@ load = ) ] ) - ( \case - [] -> pure $ Input.LoadI Nothing - [file] -> - Input.LoadI . Just - <$> unsupportedStructuredArgument "a file name" file - _ -> Left (I.help load) - ) + \case + [] -> pure $ Input.LoadI Nothing + [file] -> Input.LoadI . Just <$> unsupportedStructuredArgument "a file name" file + _ -> Left (I.help load) clear :: InputPattern clear = @@ -887,10 +776,9 @@ clear = ) ] ) - ( \case - [] -> pure Input.ClearI - _ -> Left (I.help clear) - ) + \case + [] -> pure Input.ClearI + _ -> Left (I.help clear) add :: InputPattern add = @@ -902,7 +790,7 @@ add = ( "`add` adds to the codebase all the definitions from the most recently " <> "typechecked file." ) - $ bimap P.text (Input.AddI . Set.fromList) . traverse handleNameArg + $ fmap (Input.AddI . Set.fromList) . traverse handleNameArg previewAdd :: InputPattern previewAdd = @@ -916,7 +804,7 @@ previewAdd = <> "results. Use `load` to reparse & typecheck the file if the context " <> "has changed." ) - $ bimap P.text (Input.PreviewAddI . Set.fromList) . traverse handleNameArg + $ fmap (Input.PreviewAddI . Set.fromList) . traverse handleNameArg update :: InputPattern update = @@ -962,8 +850,7 @@ updateOldNoPatch = ) ] ) - $ bimap P.text (Input.UpdateI Input.NoPatch . Set.fromList) - . traverse handleNameArg + $ fmap (Input.UpdateI Input.NoPatch . Set.fromList) . traverse handleNameArg updateOld :: InputPattern updateOld = @@ -998,10 +885,8 @@ updateOld = ] ) \case - patchStr : ws -> first P.text do - patch <- handleSplit'Arg patchStr - Input.UpdateI (Input.UsePatch patch) . Set.fromList - <$> traverse handleNameArg ws + patchStr : ws -> + Input.UpdateI . Input.UsePatch <$> handleSplit'Arg patchStr <*> fmap Set.fromList (traverse handleNameArg ws) [] -> Right $ Input.UpdateI Input.DefaultPatch mempty previewUpdate :: InputPattern @@ -1016,8 +901,7 @@ previewUpdate = <> "typechecking results. Use `load` to reparse & typecheck the file if " <> "the context has changed." ) - $ bimap P.text (Input.PreviewUpdateI . Set.fromList) - . traverse handleNameArg + $ fmap (Input.PreviewUpdateI . Set.fromList) . traverse handleNameArg view :: InputPattern view = @@ -1077,11 +961,7 @@ display = "`display` without arguments invokes a search to select a definition to display, which requires that `fzf` can be found within your PATH." ] ) - $ maybe - (Left $ I.help display) - ( fmap (Input.DisplayI Input.ConsoleLocation) - . traverse handleHashQualifiedNameArg - ) + $ maybe (Left $ I.help display) (fmap (Input.DisplayI Input.ConsoleLocation) . traverse handleHashQualifiedNameArg) . NE.nonEmpty displayTo :: InputPattern @@ -1119,10 +999,7 @@ docs = "`docs` without arguments invokes a search to select which definition to view documentation for, which requires that `fzf` can be found within your PATH." ] ) - $ maybe - (Left $ I.help docs) - (bimap P.text Input.DocsI . traverse handleHashQualifiedSplit'Arg) - . NE.nonEmpty + $ maybe (Left $ I.help docs) (fmap Input.DocsI . traverse handleHashQualifiedSplit'Arg) . NE.nonEmpty api :: InputPattern api = @@ -1144,7 +1021,7 @@ ui = help = P.wrap "`ui` opens the Local UI in the default browser.", parse = \case [] -> pure $ Input.UiI Path.relativeEmpty' - [path] -> bimap P.text Input.UiI $ handlePath'Arg path + [path] -> Input.UiI <$> handlePath'Arg path _ -> Left (I.help ui) } @@ -1307,7 +1184,7 @@ findShallow = ) ( fmap Input.FindShallowI . \case [] -> pure Path.relativeEmpty' - [path] -> first P.text $ handlePath'Arg path + [path] -> handlePath'Arg path _ -> Left (I.help findShallow) ) @@ -1345,17 +1222,9 @@ renameTerm = ("new location", Required, newNameArg) ] "`move.term foo bar` renames `foo` to `bar`." - ( \case - [oldName, newName] -> - first P.text $ - Input.MoveTermI - <$> handleHashQualifiedSplit'Arg oldName - <*> handleNewName newName - _ -> - Left . P.warnCallout $ - P.wrap - "`rename.term` takes two arguments, like `rename.term oldname newname`." - ) + \case + [oldName, newName] -> Input.MoveTermI <$> handleHashQualifiedSplit'Arg oldName <*> handleNewName newName + _ -> Left . P.warnCallout $ P.wrap "`rename.term` takes two arguments, like `rename.term oldname newname`." moveAll :: InputPattern moveAll = @@ -1367,17 +1236,9 @@ moveAll = ("new location", Required, newNameArg) ] "`move foo bar` renames the term, type, and namespace foo to bar." - ( \case - [oldName, newName] -> - first P.text $ - Input.MoveAllI - <$> handlePath'Arg oldName - <*> handleNewPath newName - _ -> - Left . P.warnCallout $ - P.wrap - "`move` takes two arguments, like `move oldname newname`." - ) + \case + [oldName, newName] -> Input.MoveAllI <$> handlePath'Arg oldName <*> handleNewPath newName + _ -> Left . P.warnCallout $ P.wrap "`move` takes two arguments, like `move oldname newname`." renameType :: InputPattern renameType = @@ -1389,17 +1250,10 @@ renameType = ("new location", Required, newNameArg) ] "`move.type foo bar` renames `foo` to `bar`." - ( \case - [oldName, newName] -> - first P.text $ - Input.MoveTypeI - <$> handleHashQualifiedSplit'Arg oldName - <*> handleNewName newName - _ -> - Left . P.warnCallout $ - P.wrap - "`rename.type` takes two arguments, like `rename.type oldname newname`." - ) + \case + [oldName, newName] -> Input.MoveTypeI <$> handleHashQualifiedSplit'Arg oldName <*> handleNewName newName + _ -> + Left . P.warnCallout $ P.wrap "`rename.type` takes two arguments, like `rename.type oldname newname`." deleteGen :: Maybe String -> ArgumentType -> String -> ([Path.HQSplit'] -> DeleteTarget) -> InputPattern deleteGen suffix queryCompletionArg target mkTarget = @@ -1438,12 +1292,9 @@ deleteGen suffix queryCompletionArg target mkTarget = I.Visible [("definition to delete", OnePlus, queryCompletionArg)] info - ( \case - [] -> Left . P.warnCallout $ P.wrap warn - queries -> - bimap P.text (Input.DeleteI . mkTarget) $ - traverse handleHashQualifiedSplit'Arg queries - ) + \case + [] -> Left . P.warnCallout $ P.wrap warn + queries -> Input.DeleteI . mkTarget <$> traverse handleHashQualifiedSplit'Arg queries delete :: InputPattern delete = deleteGen Nothing exactDefinitionTypeOrTermQueryArg "term or type" (DeleteTarget'TermOrType DeleteOutput'NoDiff) @@ -1475,9 +1326,7 @@ deleteProject = [ ("`delete.project foo`", "deletes the local project `foo`") ], parse = \case - [name] -> - bimap P.text (Input.DeleteI . DeleteTarget'Project) $ - handleProjectArg name + [name] -> Input.DeleteI . DeleteTarget'Project <$> handleProjectArg name _ -> Left (showPatternHelp deleteProject) } @@ -1494,9 +1343,7 @@ deleteBranch = ("`delete.branch /bar`", "deletes the branch `bar` in the current project") ], parse = \case - [name] -> - Input.DeleteI . DeleteTarget'ProjectBranch - <$> handleMaybeProjectBranchArg name + [name] -> Input.DeleteI . DeleteTarget'ProjectBranch <$> handleMaybeProjectBranchArg name _ -> Left (showPatternHelp deleteBranch) } where @@ -1516,15 +1363,8 @@ aliasTerm = [("term to alias", Required, exactDefinitionTermQueryArg), ("alias name", Required, newNameArg)] "`alias.term foo bar` introduces `bar` with the same definition as `foo`." $ \case - [oldName, newName] -> - first P.text $ - Input.AliasTermI - <$> handleShortHashOrHQSplit'Arg oldName - <*> handleSplit'Arg newName - _ -> - Left . warn $ - P.wrap - "`alias.term` takes two arguments, like `alias.term oldname newname`." + [oldName, newName] -> Input.AliasTermI <$> handleShortHashOrHQSplit'Arg oldName <*> handleSplit'Arg newName + _ -> Left . warn $ P.wrap "`alias.term` takes two arguments, like `alias.term oldname newname`." aliasType :: InputPattern aliasType = @@ -1534,16 +1374,9 @@ aliasType = I.Visible [("type to alias", Required, exactDefinitionTypeQueryArg), ("alias name", Required, newNameArg)] "`alias.type Foo Bar` introduces `Bar` with the same definition as `Foo`." - $ \case - [oldName, newName] -> - first P.text $ - Input.AliasTypeI - <$> handleShortHashOrHQSplit'Arg oldName - <*> handleSplit'Arg newName - _ -> - Left . warn $ - P.wrap - "`alias.type` takes two arguments, like `alias.type oldname newname`." + \case + [oldName, newName] -> Input.AliasTypeI <$> handleShortHashOrHQSplit'Arg oldName <*> handleSplit'Arg newName + _ -> Left . warn $ P.wrap "`alias.type` takes two arguments, like `alias.type oldname newname`." aliasMany :: InputPattern aliasMany = @@ -1561,12 +1394,9 @@ aliasMany = <> "creates aliases `.quux.foo.foo` and `.quux.bar.bar`." ] ) - $ \case + \case srcs@(_ : _) Cons.:> dest -> - first P.text $ - Input.AliasManyI - <$> traverse handleHashQualifiedSplitArg srcs - <*> handlePath'Arg dest + Input.AliasManyI <$> traverse handleHashQualifiedSplitArg srcs <*> handlePath'Arg dest _ -> Left (I.help aliasMany) up :: InputPattern @@ -1577,10 +1407,9 @@ up = I.Hidden [] (P.wrapColumn2 [(makeExample up [], "move current path up one level (deprecated)")]) - ( \case - [] -> Right Input.UpI - _ -> Left (I.help up) - ) + \case + [] -> Right Input.UpI + _ -> Left (I.help up) cd :: InputPattern cd = @@ -1608,9 +1437,9 @@ cd = ] ] ) - $ \case + \case [Left ".."] -> Right Input.UpI - [p] -> bimap P.text Input.SwitchBranchI $ handlePath'Arg p + [p] -> Input.SwitchBranchI <$> handlePath'Arg p _ -> Left (I.help cd) back :: InputPattern @@ -1654,13 +1483,8 @@ deleteNamespaceForce = deleteNamespaceParser :: P.Pretty CT.ColorText -> Input.Insistence -> I.Arguments -> Either (P.Pretty CT.ColorText) Input deleteNamespaceParser helpText insistence = \case - [Left "."] -> - first fromString - . pure - $ Input.DeleteI (DeleteTarget'Namespace insistence Nothing) - [p] -> - bimap P.text (Input.DeleteI . DeleteTarget'Namespace insistence . pure) $ - handleSplitArg p + [Left "."] -> first fromString . pure $ Input.DeleteI (DeleteTarget'Namespace insistence Nothing) + [p] -> Input.DeleteI . DeleteTarget'Namespace insistence . pure <$> handleSplitArg p _ -> Left helpText renameBranch :: InputPattern @@ -1671,10 +1495,8 @@ renameBranch = I.Visible [("namespace to move", Required, namespaceArg), ("new location", Required, newNameArg)] "`move.namespace foo bar` renames the path `foo` to `bar`." - $ \case - [src, dest] -> - first P.text $ - Input.MoveBranchI <$> handlePath'Arg src <*> handlePath'Arg dest + \case + [src, dest] -> Input.MoveBranchI <$> handlePath'Arg src <*> handlePath'Arg dest _ -> Left (I.help renameBranch) history :: InputPattern @@ -1693,10 +1515,8 @@ history = ) ] ) - $ \case - [src] -> - bimap P.text (Input.HistoryI (Just 10) (Just 10)) $ - handleBranchIdArg src + \case + [src] -> Input.HistoryI (Just 10) (Just 10) <$> handleBranchIdArg src [] -> pure $ Input.HistoryI (Just 10) (Just 10) (Right Path.currentPath) _ -> Left (I.help history) @@ -1721,11 +1541,8 @@ forkLocal = ) ] ) - $ \case - [src, dest] -> - Input.ForkLocalBranchI - <$> handleBranchId2Arg src - <*> handleBranchRelativePathArg dest + \case + [src, dest] -> Input.ForkLocalBranchI <$> handleBranchId2Arg src <*> handleBranchRelativePathArg dest _ -> Left (I.help forkLocal) libInstallInputPattern :: InputPattern @@ -1755,7 +1572,7 @@ libInstallInputPattern = ] ], parse = \case - [arg] -> bimap P.text Input.LibInstallI $ handleProjectMaybeBranchArg arg + [arg] -> Input.LibInstallI <$> handleProjectMaybeBranchArg arg _ -> Left (I.help libInstallInputPattern) } @@ -1775,17 +1592,10 @@ reset = ("`reset #pvfd222s8n /topic`", "reset the branch `topic` of the current project to the causal `#pvfd222s8n`.") ] ) - ( \case - [arg0] -> - Input.ResetI - <$> first P.text (handleBranchIdOrProjectArg arg0) - <*> pure Nothing - [arg0, arg1] -> - Input.ResetI - <$> first P.text (handleBranchIdOrProjectArg arg0) - <*> bimap P.text pure (handleLooseCodeOrProjectArg arg1) - _ -> Left $ I.help reset - ) + \case + [arg0] -> Input.ResetI <$> handleBranchIdOrProjectArg arg0 <*> pure Nothing + [arg0, arg1] -> Input.ResetI <$> handleBranchIdOrProjectArg arg0 <*> fmap pure (handleLooseCodeOrProjectArg arg1) + _ -> Left $ I.help reset where config = ProjectBranchSuggestionsConfig @@ -1816,7 +1626,7 @@ resetRoot = ] ) $ \case - [src] -> bimap P.text Input.ResetRootI $ handleBranchIdArg src + [src] -> Input.ResetRootI <$> handleBranchIdArg src _ -> Left (I.help resetRoot) pull :: InputPattern @@ -1949,7 +1759,7 @@ debugFuzzyOptions = P.wrap $ "or `debug.fuzzy-options merge - _`" ] ) - $ \case + \case (cmd : args) -> Input.DebugFuzzyOptionsI <$> unsupportedStructuredArgument "a command" cmd @@ -2014,13 +1824,9 @@ push = ) . \case [] -> pure Input.PushSourceTarget0 - [targetStr] -> - bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr] -> Input.PushSourceTarget1 <$> handlePushTargetArg targetStr [targetStr, sourceStr] -> - first P.text $ - Input.PushSourceTarget2 - <$> handlePushSourceArg sourceStr - <*> handlePushTargetArg targetStr + Input.PushSourceTarget2 <$> handlePushSourceArg sourceStr <*> handlePushTargetArg targetStr _ -> Left (I.help push) where suggestionsConfig = @@ -2069,13 +1875,9 @@ pushCreate = ) . \case [] -> pure Input.PushSourceTarget0 - [targetStr] -> - bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr] -> Input.PushSourceTarget1 <$> handlePushTargetArg targetStr [targetStr, sourceStr] -> - first P.text $ - Input.PushSourceTarget2 - <$> handlePushSourceArg sourceStr - <*> handlePushTargetArg targetStr + Input.PushSourceTarget2 <$> handlePushSourceArg sourceStr <*> handlePushTargetArg targetStr _ -> Left (I.help pushForce) where suggestionsConfig = @@ -2103,13 +1905,9 @@ pushForce = ) . \case [] -> pure Input.PushSourceTarget0 - [targetStr] -> - bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr] -> Input.PushSourceTarget1 <$> handlePushTargetArg targetStr [targetStr, sourceStr] -> - first P.text $ - Input.PushSourceTarget2 - <$> handlePushSourceArg sourceStr - <*> handlePushTargetArg targetStr + Input.PushSourceTarget2 <$> handlePushSourceArg sourceStr <*> handlePushTargetArg targetStr _ -> Left (I.help pushForce) where suggestionsConfig = @@ -2147,13 +1945,9 @@ pushExhaustive = ) . \case [] -> pure Input.PushSourceTarget0 - [targetStr] -> - bimap P.text Input.PushSourceTarget1 $ handlePushTargetArg targetStr + [targetStr] -> Input.PushSourceTarget1 <$> handlePushTargetArg targetStr [targetStr, sourceStr] -> - first P.text $ - Input.PushSourceTarget2 - <$> handlePushSourceArg sourceStr - <*> handlePushTargetArg targetStr + Input.PushSourceTarget2 <$> handlePushSourceArg sourceStr <*> handlePushTargetArg targetStr _ -> Left (I.help pushExhaustive) where suggestionsConfig = @@ -2182,11 +1976,10 @@ mergeOldSquashInputPattern = <> "additional history entry.", parse = \case [src, dest] -> - first P.text $ - Input.MergeLocalBranchI - <$> handleLooseCodeOrProjectArg src - <*> handleLooseCodeOrProjectArg dest - <*> pure Branch.SquashMerge + Input.MergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> handleLooseCodeOrProjectArg dest + <*> pure Branch.SquashMerge _ -> Left $ I.help mergeOldSquashInputPattern } where @@ -2229,17 +2022,15 @@ mergeOldInputPattern = ) ( \case [src] -> - first P.text $ - Input.MergeLocalBranchI - <$> handleLooseCodeOrProjectArg src - <*> pure (This Path.relativeEmpty') - <*> pure Branch.RegularMerge + Input.MergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> pure (This Path.relativeEmpty') + <*> pure Branch.RegularMerge [src, dest] -> - first P.text $ - Input.MergeLocalBranchI - <$> handleLooseCodeOrProjectArg src - <*> handleLooseCodeOrProjectArg dest - <*> pure Branch.RegularMerge + Input.MergeLocalBranchI + <$> handleLooseCodeOrProjectArg src + <*> handleLooseCodeOrProjectArg dest + <*> pure Branch.RegularMerge _ -> Left $ I.help mergeOldInputPattern ) where @@ -2303,16 +2094,8 @@ diffNamespace = ] ) ( \case - [before, after] -> - first P.text $ - Input.DiffNamespaceI - <$> handleBranchIdArg before - <*> handleBranchIdArg after - [before] -> - first P.text $ - Input.DiffNamespaceI - <$> handleBranchIdArg before - <*> pure (pure Path.currentPath) + [before, after] -> Input.DiffNamespaceI <$> handleBranchIdArg before <*> handleBranchIdArg after + [before] -> Input.DiffNamespaceI <$> handleBranchIdArg before <*> pure (pure Path.currentPath) _ -> Left $ I.help diffNamespace ) where @@ -2340,16 +2123,9 @@ mergeOldPreviewInputPattern = ] ) ( \case - [src] -> - first P.text $ - Input.PreviewMergeLocalBranchI - <$> handleLooseCodeOrProjectArg src - <*> pure (This Path.relativeEmpty') + [src] -> Input.PreviewMergeLocalBranchI <$> handleLooseCodeOrProjectArg src <*> pure (This Path.relativeEmpty') [src, dest] -> - first P.text $ - Input.PreviewMergeLocalBranchI - <$> handleLooseCodeOrProjectArg src - <*> handleLooseCodeOrProjectArg dest + Input.PreviewMergeLocalBranchI <$> handleLooseCodeOrProjectArg src <*> handleLooseCodeOrProjectArg dest _ -> Left $ I.help mergeOldPreviewInputPattern ) where @@ -2409,7 +2185,7 @@ editNamespace = [ "`edit.namespace` will load all terms and types contained within the current namespace into your scratch file. This includes definitions in namespaces, but excludes libraries.", "`edit.namespace ns1 ns2 ...` loads the terms and types contained within the provided namespaces." ], - parse = bimap P.text Input.EditNamespaceI . traverse handlePathArg + parse = fmap Input.EditNamespaceI . traverse handlePathArg } topicNameArg :: ArgumentType @@ -2705,8 +2481,7 @@ namespaceDependencies = [("namespace", Optional, namespaceArg)] "List the external dependencies of the specified namespace." $ \case - [p] -> - bimap P.text (Input.NamespaceDependenciesI . pure) $ handlePath'Arg p + [p] -> Input.NamespaceDependenciesI . pure <$> handlePath'Arg p [] -> pure (Input.NamespaceDependenciesI Nothing) _ -> Left (I.help namespaceDependencies) @@ -2831,15 +2606,9 @@ debugNameDiff = visibility = I.Hidden, args = [("before namespace", Required, namespaceArg), ("after namespace", Required, namespaceArg)], help = P.wrap "List all name changes between two causal hashes. Does not detect patch changes.", - parse = - ( \case - [from, to] -> - first P.text $ - Input.DebugNameDiffI - <$> handleShortCausalHashArg from - <*> handleShortCausalHashArg to - _ -> Left (I.help debugNameDiff) - ) + parse = \case + [from, to] -> Input.DebugNameDiffI <$> handleShortCausalHashArg from <*> handleShortCausalHashArg to + _ -> Left (I.help debugNameDiff) } test :: InputPattern @@ -2867,7 +2636,7 @@ test = ) . \case [] -> pure Path.empty - [pathString] -> first P.text $ handlePathArg pathString + [pathString] -> handlePathArg pathString _ -> Left $ I.help test } @@ -2904,10 +2673,10 @@ docsToHtml = ) ] ) - $ \case + \case [namespacePath, destinationFilePath] -> Input.DocsToHtmlI - <$> first P.text (handlePath'Arg namespacePath) + <$> handlePath'Arg namespacePath <*> unsupportedStructuredArgument "a file name" destinationFilePath _ -> Left $ showPatternHelp docsToHtml @@ -2924,9 +2693,8 @@ docToMarkdown = ) ] ) - $ \case - [docNameText] -> - bimap P.text Input.DocToMarkdownI $ handleNameArg docNameText + \case + [docNameText] -> Input.DocToMarkdownI <$> handleNameArg docNameText _ -> Left $ showPatternHelp docToMarkdown execute :: InputPattern @@ -2962,7 +2730,7 @@ saveExecuteResult = <> "as `name`." ) $ \case - [w] -> first P.text $ Input.SaveExecuteResultI <$> handleNameArg w + [w] -> Input.SaveExecuteResultI <$> handleNameArg w _ -> Left $ showPatternHelp saveExecuteResult ioTest :: InputPattern @@ -3085,10 +2853,8 @@ createAuthor = \case symbolStr : authorStr@(_ : _) -> Input.CreateAuthorI - <$> first P.text (handleRelativeNameSegmentArg symbolStr) - <*> fmap - (parseAuthorName . unwords) - (traverse (unsupportedStructuredArgument "text") authorStr) + <$> handleRelativeNameSegmentArg symbolStr + <*> fmap (parseAuthorName . unwords) (traverse (unsupportedStructuredArgument "text") authorStr) _ -> Left $ showPatternHelp createAuthor where -- let's have a real parser in not too long @@ -3172,10 +2938,8 @@ projectCreate = ("`project.create foo`", "creates a project named `foo`") ], parse = \case - [] -> Right (Input.ProjectCreateI True Nothing) - [name] -> - bimap P.text (Input.ProjectCreateI True . pure) $ - handleProjectArg name + [] -> pure $ Input.ProjectCreateI True Nothing + [name] -> Input.ProjectCreateI True . pure <$> handleProjectArg name _ -> Left $ showPatternHelp projectCreate } @@ -3192,10 +2956,8 @@ projectCreateEmptyInputPattern = ("`project.create-empty foo`", "creates an empty project named `foo`") ], parse = \case - [] -> Right (Input.ProjectCreateI False Nothing) - [name] -> - bimap P.text (Input.ProjectCreateI False . pure) $ - handleProjectArg name + [] -> pure $ Input.ProjectCreateI False Nothing + [name] -> Input.ProjectCreateI False . pure <$> handleProjectArg name _ -> Left $ showPatternHelp projectCreateEmptyInputPattern } @@ -3211,8 +2973,7 @@ projectRenameInputPattern = [ ("`project.rename foo`", "renames the current project to `foo`") ], parse = \case - [nameString] -> - bimap P.text Input.ProjectRenameI $ handleProjectArg nameString + [nameString] -> Input.ProjectRenameI <$> handleProjectArg nameString _ -> Left (showPatternHelp projectRenameInputPattern) } @@ -3231,9 +2992,7 @@ projectSwitch = ("`switch /bar`", "switches to the branch `bar` in the current project") ], parse = \case - [name] -> - bimap P.text Input.ProjectSwitchI $ - handleProjectAndBranchNamesArg name + [name] -> Input.ProjectSwitchI <$> handleProjectAndBranchNamesArg name _ -> Left (showPatternHelp projectSwitch) } where @@ -3269,8 +3028,7 @@ branchesInputPattern = ], parse = \case [] -> Right (Input.BranchesI Nothing) - [nameString] -> - bimap P.text (Input.BranchesI . pure) $ handleProjectArg nameString + [nameString] -> Input.BranchesI . pure <$> handleProjectArg nameString _ -> Left (showPatternHelp branchesInputPattern) } @@ -3293,11 +3051,9 @@ branchInputPattern = parse = \case [source0, name] -> Input.BranchI . Input.BranchSourceI'LooseCodeOrProject - <$> first P.text (handleLooseCodeOrProjectArg source0) + <$> handleLooseCodeOrProjectArg source0 <*> handleMaybeProjectBranchArg name - [name] -> - Input.BranchI Input.BranchSourceI'CurrentContext - <$> handleMaybeProjectBranchArg name + [name] -> Input.BranchI Input.BranchSourceI'CurrentContext <$> handleMaybeProjectBranchArg name _ -> Left $ showPatternHelp branchInputPattern } where @@ -3340,8 +3096,7 @@ branchRenameInputPattern = P.wrapColumn2 [("`branch.rename foo`", "renames the current branch to `foo`")], parse = \case - [name] -> - bimap P.text Input.BranchRenameI $ handleProjectBranchNameArg name + [name] -> Input.BranchRenameI <$> handleProjectBranchNameArg name _ -> Left (showPatternHelp branchRenameInputPattern) } @@ -3375,16 +3130,11 @@ clone = ) ], parse = \case - [remoteNames] -> do - first P.text $ - Input.CloneI - <$> handleProjectAndBranchNamesArg remoteNames - <*> pure Nothing + [remoteNames] -> Input.CloneI <$> handleProjectAndBranchNamesArg remoteNames <*> pure Nothing [remoteNames, localNames] -> - first P.text $ - Input.CloneI - <$> handleProjectAndBranchNamesArg remoteNames - <*> fmap pure (handleProjectAndBranchNamesArg localNames) + Input.CloneI + <$> handleProjectAndBranchNamesArg remoteNames + <*> fmap pure (handleProjectAndBranchNamesArg localNames) _ -> Left $ showPatternHelp clone } @@ -3417,10 +3167,7 @@ upgrade = "`upgrade old new` upgrades library dependency `lib.old` to `lib.new`, and, if successful, deletes `lib.old`.", parse = \case [oldString, newString] -> - first P.text $ - Input.UpgradeI - <$> handleRelativeNameSegmentArg oldString - <*> handleRelativeNameSegmentArg newString + Input.UpgradeI <$> handleRelativeNameSegmentArg oldString <*> handleRelativeNameSegmentArg newString _ -> Left $ I.help upgrade } From f8474ff457f2d3a28bba854376e3b16b819e5168 Mon Sep 17 00:00:00 2001 From: Greg Pfeil Date: Tue, 28 May 2024 16:54:33 -0600 Subject: [PATCH 10/10] Handle `SCH` carefully in `StructuredArgument`s When `StructuredArgument`s are used as an input, preserve the entire hash. When printed, take the length as an optional argument (and show the full hash when unavailable). --- .../src/Unison/Codebase/ShortCausalHash.hs | 9 +++++++ .../src/Unison/Codebase/Editor/HandleInput.hs | 3 ++- .../src/Unison/Codebase/Editor/Output.hs | 4 ++-- .../src/Unison/CommandLine/InputPatterns.hs | 24 +++++++------------ .../src/Unison/CommandLine/OutputMessages.hs | 3 ++- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/parser-typechecker/src/Unison/Codebase/ShortCausalHash.hs b/parser-typechecker/src/Unison/Codebase/ShortCausalHash.hs index 75335193379..7e8b40e75bd 100644 --- a/parser-typechecker/src/Unison/Codebase/ShortCausalHash.hs +++ b/parser-typechecker/src/Unison/Codebase/ShortCausalHash.hs @@ -2,6 +2,7 @@ module Unison.Codebase.ShortCausalHash ( toString, toHash, fromHash, + fromFullHash, fromText, ShortCausalHash (..), ) @@ -27,6 +28,14 @@ fromHash :: (Coercible h Hash.Hash) => Int -> h -> ShortCausalHash fromHash len = ShortCausalHash . Text.take len . Hash.toBase32HexText . coerce +-- | This allows a full hash to be preserved as a `ShortCausalHash`. +-- +-- `ShortCausalHash` is used for input when we expect a user to enter a hash on the command line, so they aren’t +-- required to enter the full hash. However, these inputs may also come from an internal source, and in such cases, +-- there is no reason to truncate the hash. +fromFullHash :: (Coercible h Hash.Hash) => h -> ShortCausalHash +fromFullHash = ShortCausalHash . Hash.toBase32HexText . coerce + -- abc -> SCH abc -- #abc -> SCH abc fromText :: Text -> Maybe ShortCausalHash diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index 9f934f574f1..26eb5723ffc 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -812,8 +812,9 @@ loop e = do ListDependenciesI hq -> handleDependencies hq NamespaceDependenciesI path -> handleNamespaceDependencies path DebugNumberedArgsI -> do + schLength <- Cli.runTransaction Codebase.branchHashLength numArgs <- use #numberedArgs - Cli.respond (DumpNumberedArgs numArgs) + Cli.respond (DumpNumberedArgs schLength numArgs) DebugTypecheckedUnisonFileI -> do hqLength <- Cli.runTransaction Codebase.hashLength uf <- Cli.expectLatestTypecheckedFile diff --git a/unison-cli/src/Unison/Codebase/Editor/Output.hs b/unison-cli/src/Unison/Codebase/Editor/Output.hs index 4aa79eed30f..ca67d3e4b97 100644 --- a/unison-cli/src/Unison/Codebase/Editor/Output.hs +++ b/unison-cli/src/Unison/Codebase/Editor/Output.hs @@ -27,7 +27,7 @@ import U.Codebase.HashTags (CausalHash) import U.Codebase.Sqlite.Project qualified as Sqlite import U.Codebase.Sqlite.ProjectBranch qualified as Sqlite import Unison.Auth.Types (CredentialFailure) -import Unison.Cli.MergeTypes (MergeSourceOrTarget, MergeSourceAndTarget) +import Unison.Cli.MergeTypes (MergeSourceAndTarget, MergeSourceOrTarget) import Unison.Cli.Share.Projects.Types qualified as Share import Unison.Codebase.Editor.Input import Unison.Codebase.Editor.Output.BranchDiff (BranchDiffOutput) @@ -302,7 +302,7 @@ data Output | ListDependencies PPE.PrettyPrintEnv (Set LabeledDependency) [HQ.HashQualified Name] [HQ.HashQualified Name] -- types, terms | -- | List dependents of a type or term. ListDependents PPE.PrettyPrintEnv (Set LabeledDependency) [HQ.HashQualified Name] [HQ.HashQualified Name] -- types, terms - | DumpNumberedArgs NumberedArgs + | DumpNumberedArgs HashLength NumberedArgs | DumpBitBooster CausalHash (Map CausalHash [CausalHash]) | DumpUnisonFileHashes Int [(Name, Reference.Id)] [(Name, Reference.Id)] [(Name, Reference.Id)] | BadName Text diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index 7f7524dd2ba..dac33790491 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -220,14 +220,8 @@ import Unison.Util.Monoid (intercalateMap) import Unison.Util.Pretty qualified as P import Unison.Util.Pretty.MegaParsec (prettyPrintParseError) --- | --- --- __FIXME__: Don’t hardcode this -schLength :: Int -schLength = 10 - -formatStructuredArgument :: StructuredArgument -> Text -formatStructuredArgument = \case +formatStructuredArgument :: Maybe Int -> StructuredArgument -> Text +formatStructuredArgument schLength = \case SA.AbsolutePath path -> into @Text $ show path SA.Name name -> Name.toText name SA.HashQualified hqName -> HQ.toText hqName @@ -237,7 +231,7 @@ formatStructuredArgument = \case -- also: ShortHash.toText . Reference.toShortHash SA.Ref reference -> Reference.toText reference -- also: ("#" <>) . Hash.toBase32HexText . unCausalHash - SA.Namespace causalHash -> ("#" <>) . SCH.toText $ SCH.fromHash schLength causalHash + SA.Namespace causalHash -> ("#" <>) . SCH.toText $ maybe SCH.fromFullHash SCH.fromHash schLength causalHash SA.NameWithBranchPrefix absBranchId name -> prefixBranchId absBranchId name SA.HashQualifiedWithBranchPrefix absBranchId hq'Name -> HQ'.toTextWith (prefixBranchId absBranchId) hq'Name SA.ShallowListEntry path entry -> entryToHQText path entry @@ -270,7 +264,7 @@ formatStructuredArgument = \case -- command /should/ accept a structured argument of some type, but currently -- wants a `String`. unifyArgument :: I.Argument -> String -unifyArgument = either id (Text.unpack . formatStructuredArgument) +unifyArgument = either id (Text.unpack . formatStructuredArgument Nothing) showPatternHelp :: InputPattern -> P.Pretty CT.ColorText showPatternHelp i = @@ -306,7 +300,7 @@ wrongStructuredArgument :: Text -> StructuredArgument -> P.Pretty CT.ColorText wrongStructuredArgument expected actual = P.text $ expectedButActually expected - (formatStructuredArgument actual) + (formatStructuredArgument Nothing actual) case actual of SA.Ref _ -> "a reference" SA.Name _ -> "a name" @@ -467,7 +461,7 @@ handleBranchIdArg = SA.Name name -> pure . pure $ Path.fromName' name SA.NameWithBranchPrefix mprefix name -> pure . pure . Path.fromName' $ either (const name) (Name.makeAbsolute . flip Path.prefixName name) mprefix - SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash + SA.Namespace hash -> pure . Left $ SCH.fromFullHash hash otherNumArg -> Left $ wrongStructuredArgument "a branch id" otherNumArg handleBranchIdOrProjectArg :: @@ -477,7 +471,7 @@ handleBranchIdOrProjectArg = either (maybe (Left $ P.text "Expected a branch or project, but it’s not") pure . branchIdOrProject) \case - SA.Namespace hash -> pure . This . Left $ SCH.fromHash schLength hash + SA.Namespace hash -> pure . This . Left $ SCH.fromFullHash hash SA.AbsolutePath path -> pure . This . pure $ Path.absoluteToPath' path SA.Name name -> pure . This . pure $ Path.fromName' name SA.NameWithBranchPrefix (Left _) name -> pure . This . pure $ Path.fromName' name @@ -509,7 +503,7 @@ handleBranchId2Arg = either Input.parseBranchId2 \case - SA.Namespace hash -> pure . Left $ SCH.fromHash schLength hash + SA.Namespace hash -> pure . Left $ SCH.fromFullHash hash SA.AbsolutePath path -> pure . pure . LoosePath $ Path.absoluteToPath' path SA.Name name -> pure . pure . LoosePath $ Path.fromName' name SA.NameWithBranchPrefix (Left _) name -> pure . pure . LoosePath $ Path.fromName' name @@ -584,7 +578,7 @@ handleShortCausalHashArg = either (first (P.text . Text.pack) . Input.parseShortCausalHash) \case - SA.Namespace hash -> pure $ SCH.fromHash schLength hash + SA.Namespace hash -> pure $ SCH.fromFullHash hash otherNumArg -> Left $ wrongStructuredArgument "a causal hash" otherNumArg handleShortHashOrHQSplit'Arg :: diff --git a/unison-cli/src/Unison/CommandLine/OutputMessages.hs b/unison-cli/src/Unison/CommandLine/OutputMessages.hs index 43e6c9fbbb2..0054183d4a8 100644 --- a/unison-cli/src/Unison/CommandLine/OutputMessages.hs +++ b/unison-cli/src/Unison/CommandLine/OutputMessages.hs @@ -1545,7 +1545,8 @@ notifyUser dir = \case prettyNamespaceKey dest <> "is already up-to-date with" <> P.group (prettyNamespaceKey src <> ".") - DumpNumberedArgs args -> pure . P.numberedList $ fmap (P.text . IP.formatStructuredArgument) args + DumpNumberedArgs schLength args -> + pure . P.numberedList $ fmap (P.text . IP.formatStructuredArgument (pure schLength)) args NoConflictsOrEdits -> pure (P.okCallout "No conflicts or edits in progress.") HelpMessage pat -> pure $ IP.showPatternHelp pat