Enhanced completions for the assistant (#4420)

* Enhanced completions for the assistant

changelog_begin

- [DAML Assistant] The assistant can now do completions for SDK
  commands, e.g., ``daml ledger upl<TAB>`` will complete to ``daml
  ledger upload-dar``.

changelog_end

* Apply suggestions from code review

Co-Authored-By: associahedron <231829+associahedron@users.noreply.github.com>

Co-authored-by: associahedron <231829+associahedron@users.noreply.github.com>
This commit is contained in:
Moritz Kiefer 2020-02-06 15:16:12 +01:00 committed by GitHub
parent 30afc76d23
commit fc87953ed4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 31 deletions

View File

@ -63,8 +63,8 @@ sdkVersionFromSdkConfig :: SdkConfig -> Either ConfigError SdkVersion
sdkVersionFromSdkConfig = querySdkConfigRequired ["version"] sdkVersionFromSdkConfig = querySdkConfigRequired ["version"]
-- | Read sdk config to get list of sdk commands. -- | Read sdk config to get list of sdk commands.
listSdkCommands :: SdkConfig -> Either ConfigError [SdkCommandInfo] listSdkCommands :: SdkPath -> EnrichedCompletion -> SdkConfig -> Either ConfigError [SdkCommandInfo]
listSdkCommands = querySdkConfigRequired ["commands"] listSdkCommands sdkPath enriched sdkConf = map (\f -> f sdkPath enriched) <$> querySdkConfigRequired ["commands"] sdkConf
-- | Query the daml config by passing a path to the desired property. -- | Query the daml config by passing a path to the desired property.
-- See 'queryConfig' for more details. -- See 'queryConfig' for more details.

View File

@ -1,6 +1,6 @@
-- Copyright (c) 2020 The DAML Authors. All rights reserved. -- Copyright (c) 2020 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0 -- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE FlexibleInstances #-}
module DA.Daml.Project.Types module DA.Daml.Project.Types
( module DA.Daml.Project.Types ( module DA.Daml.Project.Types
@ -136,12 +136,34 @@ data SdkCommandInfo = SdkCommandInfo
, sdkCommandPath :: SdkCommandPath -- ^ file path of binary relative to sdk directory , sdkCommandPath :: SdkCommandPath -- ^ file path of binary relative to sdk directory
, sdkCommandArgs :: SdkCommandArgs -- ^ extra args to pass before user-supplied args (defaults to []) , sdkCommandArgs :: SdkCommandArgs -- ^ extra args to pass before user-supplied args (defaults to [])
, sdkCommandDesc :: Maybe Text -- ^ description of sdk command (optional) , sdkCommandDesc :: Maybe Text -- ^ description of sdk command (optional)
, sdkCommandForwardCompletion :: ForwardCompletion -- ^ Can we forward optparse-applicative completions to
-- this command
, sdkCommandSdkPath :: SdkPath -- ^ SDK path so we can get the absolute path to the command.
} deriving (Eq, Show) } deriving (Eq, Show)
instance Y.FromJSON SdkCommandInfo where data ForwardCompletion
parseJSON = Y.withObject "SdkCommandInfo" $ \p -> = Forward EnrichedCompletion -- ^ Forward completions
SdkCommandInfo | NoForward -- ^ No forwarding, fall back to basic completion
<$> (p Y..: "name") deriving (Eq, Show)
<*> (p Y..: "path")
<*> fmap (fromMaybe (SdkCommandArgs [])) (p Y..:? "args") -- | True if --bash-completion-enriched was part of argv.
<*> (p Y..:? "desc") newtype EnrichedCompletion = EnrichedCompletion { getEnrichedCompletion :: Bool }
deriving (Eq, Show)
hasEnrichedCompletion :: [String] -> EnrichedCompletion
hasEnrichedCompletion = EnrichedCompletion . elem "--bash-completion-enriched"
instance Y.FromJSON (SdkPath -> EnrichedCompletion -> SdkCommandInfo) where
parseJSON = Y.withObject "SdkCommandInfo" $ \p -> do
name <- p Y..: "name"
path <- p Y..: "path"
args <- fmap (fromMaybe (SdkCommandArgs [])) (p Y..:? "args")
desc <- p Y..:? "desc"
completion <- fromMaybe False <$> p Y..:? "completion"
return $ \sdkPath enriched -> SdkCommandInfo
name
path
args
desc
(if completion then Forward enriched else NoForward)
sdkPath

View File

@ -14,6 +14,7 @@ import DA.Daml.Assistant.Command
import DA.Daml.Assistant.Version import DA.Daml.Assistant.Version
import DA.Daml.Assistant.Install import DA.Daml.Assistant.Install
import DA.Daml.Assistant.Util import DA.Daml.Assistant.Util
import System.Environment (getArgs)
import System.FilePath import System.FilePath
import System.Directory import System.Directory
import System.Process.Typed import System.Process.Typed
@ -53,24 +54,26 @@ main = displayErrors $ do
-- So if we can't find it, let the user know. This will happen whenever -- So if we can't find it, let the user know. This will happen whenever
-- auto-install is disabled and the project or environment specify a -- auto-install is disabled and the project or environment specify a
-- missing SDK version. -- missing SDK version.
when (isNothing envSdkPath) $ do case envSdkPath of
let installTarget Nothing -> do
| Just v <- envSdkVersion = versionToString v let installTarget
| otherwise = "latest" | Just v <- envSdkVersion = versionToString v
hPutStr stderr . unlines $ | otherwise = "latest"
[ "DAML SDK not installed. Cannot run command without SDK." hPutStr stderr . unlines $
, "To proceed, please install the SDK by running:" [ "DAML SDK not installed. Cannot run command without SDK."
, "" , "To proceed, please install the SDK by running:"
, " daml install " <> installTarget , ""
, "" , " daml install " <> installTarget
] , ""
exitFailure ]
exitFailure
sdkConfig <- readSdkConfig (fromJust envSdkPath) Just sdkPath -> do
sdkCommands <- fromRightM throwIO (listSdkCommands sdkConfig) sdkConfig <- readSdkConfig sdkPath
userCommand <- getCommand sdkCommands enriched <- hasEnrichedCompletion <$> getArgs
versionChecks env sdkCommands <- fromRightM throwIO (listSdkCommands sdkPath enriched sdkConfig)
handleCommand env userCommand userCommand <- getCommand sdkCommands
versionChecks env
handleCommand env userCommand
-- | Perform version checks, i.e. warn user if project SDK version or assistant SDK -- | Perform version checks, i.e. warn user if project SDK version or assistant SDK
-- versions are out of date with the latest known release. -- versions are out of date with the latest known release.

View File

@ -18,8 +18,10 @@ import DA.Daml.Assistant.Types
import Data.List import Data.List
import Data.Maybe import Data.Maybe
import Data.Foldable import Data.Foldable
import Options.Applicative.Types
import Options.Applicative.Extended import Options.Applicative.Extended
import System.Environment import System.Environment
import System.FilePath
import Data.Either.Extra import Data.Either.Extra
import Control.Exception.Safe import Control.Exception.Safe
import System.Process import System.Process
@ -60,8 +62,30 @@ dispatch info = subcommand
(unwrapSdkCommandName $ sdkCommandName info) (unwrapSdkCommandName $ sdkCommandName info)
(fromMaybe "" $ sdkCommandDesc info) (fromMaybe "" $ sdkCommandDesc info)
forwardOptions forwardOptions
(Dispatch info . UserCommandArgs <$> (Dispatch info . UserCommandArgs <$> sdkCommandArgsParser info)
many (strArgument (metavar "ARGS" <> completer defaultCompleter)))
sdkCommandArgsParser :: SdkCommandInfo -> Parser [String]
sdkCommandArgsParser info = fromM (go (unwrapSdkCommandArgs $ sdkCommandArgs info))
where go args = do
mx <- oneM $ optional $ strArgument $ completer $
case sdkCommandForwardCompletion info of
Forward enriched -> nestedCompl enriched args
NoForward -> defaultCompleter
case mx of
Nothing -> return []
Just x -> (x :) <$> go (args ++ [x])
nestedCompl enriched args = mkCompleter $ \arg -> do
let path = unwrapSdkPath (sdkCommandSdkPath info) </> unwrapSdkCommandPath (sdkCommandPath info)
let createProc = proc
path
( [ "--bash-completion-enriched" | getEnrichedCompletion enriched ]
<>
("--bash-completion-index"
: show (length args + 1)
: concatMap (\x -> ["--bash-completion-word", x]) ("daml" : args ++ [arg])
))
stdout <- readCreateProcess createProc (repeat ' ')
pure $ lines stdout
commandParser :: [SdkCommandInfo] -> Parser Command commandParser :: [SdkCommandInfo] -> Parser Command
commandParser cmds | (hidden, visible) <- partition isHidden cmds = asum commandParser cmds | (hidden, visible) <- partition isHidden cmds = asum
@ -104,7 +128,7 @@ readSdkVersion =
eitherReader (mapLeft displayException . parseVersion . pack) eitherReader (mapLeft displayException . parseVersion . pack)
-- | Completer that uses the builtin bash completion. -- | Completer that uses the builtin bash completion.
-- We use this to ensure that `daml build -o foo` will still complete to `daml build -o foobar.dar`. -- We use this as a fallback for commands that do not use optparse-applicative to at least get file completions.
defaultCompleter :: Completer defaultCompleter :: Completer
defaultCompleter = mkCompleter $ \word -> do defaultCompleter = mkCompleter $ \word -> do
-- The implementation here is a variant of optparse-applicatives `bashCompleter`. -- The implementation here is a variant of optparse-applicatives `bashCompleter`.

View File

@ -4,36 +4,45 @@ commands:
path: daml-helper/daml-helper path: daml-helper/daml-helper
desc: "Launch DAML Studio" desc: "Launch DAML Studio"
args: ["studio"] args: ["studio"]
completion: true
- name: new - name: new
path: daml-helper/daml-helper path: daml-helper/daml-helper
desc: "Create a new DAML project" desc: "Create a new DAML project"
args: ["new"] args: ["new"]
completion: true
- name: migrate - name: migrate
path: daml-helper/daml-helper path: daml-helper/daml-helper
args: ["migrate"] args: ["migrate"]
completion: true
- name: init - name: init
path: daml-helper/daml-helper path: daml-helper/daml-helper
desc: "Configure a folder as a DAML project" desc: "Configure a folder as a DAML project"
args: ["init"] args: ["init"]
completion: true
- name: build - name: build
path: damlc/damlc path: damlc/damlc
args: ["build", "--project-check"] args: ["build", "--project-check"]
desc: "Build the DAML project into a DAR file" desc: "Build the DAML project into a DAR file"
completion: true
- name: test - name: test
path: damlc/damlc path: damlc/damlc
args: ["test"] args: ["test"]
desc: "Run the scenarios in the given DAML file and all dependencies" desc: "Run the scenarios in the given DAML file and all dependencies"
completion: true
- name: start - name: start
path: daml-helper/daml-helper path: daml-helper/daml-helper
args: ["start"] args: ["start"]
desc: "Launch Sandbox and Navigator for current DAML project" desc: "Launch Sandbox and Navigator for current DAML project"
completion: true
- name: clean - name: clean
path: damlc/damlc path: damlc/damlc
args: ["clean", "--project-check"] args: ["clean", "--project-check"]
desc: "Delete build artifacts from project folder" desc: "Delete build artifacts from project folder"
completion: true
- name: damlc - name: damlc
path: damlc/damlc path: damlc/damlc
desc: "Run the DAML compiler" desc: "Run the DAML compiler"
completion: true
- name: sandbox - name: sandbox
path: daml-helper/daml-helper path: daml-helper/daml-helper
desc: "Launch the Sandbox" desc: "Launch the Sandbox"
@ -50,14 +59,17 @@ commands:
path: daml-helper/daml-helper path: daml-helper/daml-helper
desc: "Interact with a DAML ledger (experimental)" desc: "Interact with a DAML ledger (experimental)"
args: ["ledger"] args: ["ledger"]
completion: true
- name: codegen - name: codegen
path: daml-helper/daml-helper path: daml-helper/daml-helper
desc: "Run a language bindings code generation tool" desc: "Run a language bindings code generation tool"
args: ["codegen"] args: ["codegen"]
completion: true
- name: deploy - name: deploy
path: daml-helper/daml-helper path: daml-helper/daml-helper
desc: "Deploy DAML project to a ledger (experimental)" desc: "Deploy DAML project to a ledger (experimental)"
args: ["deploy"] args: ["deploy"]
completion: true
- name: ide - name: ide
path: damlc/damlc path: damlc/damlc
args: ["lax", "ide"] args: ["lax", "ide"]