Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pipe placeholders #4

Merged
merged 3 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 70 additions & 25 deletions src/Nixon/Config/Markdown.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import Data.Maybe (fromMaybe, mapMaybe)
import Data.Text (pack, strip)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Data.Tuple (swap)
import qualified Data.Yaml as Yaml
import Nixon.Command (bg, json, (<!))
import qualified Nixon.Command as Cmd
Expand Down Expand Up @@ -279,40 +278,86 @@ parseCommandName = first (T.pack . show) . P.parse parser ""
parseCommandArgs :: Parser [Cmd.Placeholder]
parseCommandArgs =
P.choice
[ (:) <$> parseCommandArg' <*> parseCommandArgs,
[ (:) <$> parseCommandPlaceholder <*> parseCommandArgs,
P.anyChar *> parseCommandArgs,
[] <$ P.eof
]

-- | Convenience wrapper for running placeholder parser
parseCommandArg :: String -> Either String Cmd.Placeholder
parseCommandArg = first show . P.parse parseCommandArg' "" . T.pack
parseCommandArg = first show . P.parse parseCommandPlaceholder "" . T.pack

parseCommandArg' :: Parser Cmd.Placeholder
parseCommandArg' = do
parseCommandPlaceholder :: Parser Cmd.Placeholder
parseCommandPlaceholder = do
let startCmdArg =
(Cmd.Stdin <$ P.char '<')
<|> (Cmd.Arg <$ P.char '$')
<|> (Cmd.EnvVar . T.pack <$> P.many (P.alphaNum <|> P.char '_') <* P.char '=')
placeholderType <- P.try $ startCmdArg <* P.char '{'
(name, fields, multiple) <- parseSpec <* P.char '}'
let fixup = T.replace "-" "_"
placeholderWithName = case placeholderType of
Cmd.EnvVar "" -> Cmd.EnvVar $ fixup name
Cmd.EnvVar alias -> Cmd.EnvVar $ fixup alias
same -> same
pure $ Cmd.Placeholder placeholderWithName name fields multiple []

parseSpec :: Parser (Text, [Integer], Bool)
parseSpec = do
name <- T.pack <$> P.many1 (P.noneOf ":}")
P.option (name, [], False) $ do
_ <- P.char ':'
-- Accept fields and multiple in any order
(fields, multiple) <-
((,) <$> parseFields <*> parseMultiple)
<|> (fmap swap . (,) <$> parseMultiple <*> P.option [] parseFields)
pure (name, fields, multiple)
placeholder <- do
name <- T.pack <$> P.many1 (P.noneOf " :|}")
let fixup = T.replace "-" "_"
placeholderWithName = case placeholderType of
Cmd.EnvVar "" -> Cmd.EnvVar $ fixup name
Cmd.EnvVar alias -> Cmd.EnvVar $ fixup alias
same -> same
pure $ Cmd.Placeholder placeholderWithName name [] False []
parsePlaceholderModifiers placeholder <* P.char '}'

-- | Parse the "modifiers" which affect how command placeholders are handled.
--
-- This includes:
--
-- * If the placeholder can select multiple values.
-- * Which fields to include from selections.
-- * TODO: Interpret the input as JSON.
-- * TODO: Access JSON attributes through jq-like expressions.
--
-- Formats take on two types, shorthand and pipelines. The following are
-- equivalent:
--
-- Pipeline: `some-command ${placeholder | multiple | fields 1,3}`
-- Shorthand: `some-command ${placeholder:m1,3}`
parsePlaceholderModifiers :: Cmd.Placeholder -> Parser Cmd.Placeholder
parsePlaceholderModifiers placeholder = do
P.choice
[ parsePipeModifiers placeholder,
parseColonModifiers placeholder,
pure placeholder
]
where
parseMultiple = P.option False (True <$ P.char 'm')
parseFields = mapMaybe readMaybe <$> (P.many1 P.digit `P.sepBy1` P.char ',')
-- Parse `command-name | fields 1,2 | multiple`
parsePipeModifiers :: Cmd.Placeholder -> Parser Cmd.Placeholder
parsePipeModifiers p = do
_ <- P.many P.space *> P.char '|' *> P.many P.space
p' <-
P.choice
[ parsePipeFields p,
parsePipeMultiple p
]
_ <- P.many P.space
P.option p' (parsePipeModifiers p')

parsePipeFields p = P.string "fields" *> P.many P.space *> parseFields p

parsePipeMultiple p = (P.string "multi" :: Parser String) $> p {Cmd.multiple = True}

-- Parse `command-name:1,2`
parseColonModifiers :: Cmd.Placeholder -> Parser Cmd.Placeholder
parseColonModifiers p = do
_ <- P.char ':'
-- Accept fields and multiple in any order
(parseFields p >>= perhaps parseMultiple) <|> (parseMultiple p >>= perhaps parseFields)

parseFields :: Cmd.Placeholder -> Parser Cmd.Placeholder
parseFields p' = do
fields <- mapMaybe readMaybe <$> (P.many1 P.digit `P.sepBy1` P.char ',')
pure $ p' {Cmd.fields = fields}

parseMultiple :: Cmd.Placeholder -> Parser Cmd.Placeholder
parseMultiple p' = do
multiple <- P.option False (True <$ P.char 'm')
pure $ p' {Cmd.multiple = multiple}

-- Try a parser or default to `value`
perhaps parser value = P.option value (parser value)
23 changes: 22 additions & 1 deletion test/Test/Nixon/Config/Markdown.hs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ command_tests = describe "commands section" $ do
"# `three`",
"``` bash ${arg-three:1,2}",
"echo Hello \"$1\"",
"```",
"# `four`",
"``` bash ${arg-four | fields 1,2 | multi}",
"echo Hello \"$1\"",
"```"
]
selector
Expand All @@ -436,7 +440,8 @@ command_tests = describe "commands section" $ do
`shouldBe` Right
[ (Bash, [Placeholder Arg "arg-one" [] False []]),
(Bash, [Placeholder Arg "arg-two" [] True []]),
(Bash, [Placeholder Arg "arg-three" [1, 2] False []])
(Bash, [Placeholder Arg "arg-three" [1, 2] False []]),
(Bash, [Placeholder Arg "arg-four" [1, 2] True []])
]

it "complains on both header & code block placeholders" $ do
Expand Down Expand Up @@ -523,6 +528,14 @@ parse_command_name_tests = describe "parseCommandName" $ do
parseCommandName "cat ${arg:m}"
`shouldBe` Right ("cat", [Placeholder Arg "arg" [] True []])

it "parses arg modifiers" $ do
parseCommandName "cat ${arg | fields 1,3}"
`shouldBe` Right ("cat", [Placeholder Arg "arg" [1, 3] False []])

it "parses arg modifiers" $ do
parseCommandName "cat ${arg | multi}"
`shouldBe` Right ("cat", [Placeholder Arg "arg" [] True []])

it "parses stdin arg modifiers" $ do
parseCommandName "cat <{arg:m}"
`shouldBe` Right ("cat", [Placeholder Stdin "arg" [] True []])
Expand All @@ -535,6 +548,14 @@ parse_command_name_tests = describe "parseCommandName" $ do
parseCommandName "cat <{arg:1,3,5m}"
`shouldBe` Right ("cat", [Placeholder Stdin "arg" [1, 3, 5] True []])

it "parses arg field and pipe fields" $ do
parseCommandName "cat <{arg | fields 1,3,5}"
`shouldBe` Right ("cat", [Placeholder Stdin "arg" [1, 3, 5] False []])

it "parses arg field, pipe fields and pipe multiple" $ do
parseCommandName "cat <{arg | fields 1,3,5 | multi}"
`shouldBe` Right ("cat", [Placeholder Stdin "arg" [1, 3, 5] True []])

it "parses text and placeholder part" $ do
parseCommandName "cat \"${arg}\""
`shouldBe` Right ("cat", [Placeholder Arg "arg" [] False []])
Expand Down
Loading