In a Turtle script for getting passwords from a keyring, calling ssh-add
with those passwords so they don't have to be filled out manually, is the following function:
processKey :: (T.Text, T.Text) -> Shell Line -> IO ()
processKey (kn, str) pwds = do
-- Not sure why (text str) is needed here, but it won't typecheck without
-- it (despite OverloadedStrings).
let expectArg = do
ml <- grep (contains (text str)) pwds
let pass = getPwd $ cut tab (format l ml)
return $ T.unlines [ "<< EOF"
, "spawn ssh-add"
, "expect \"Enter passphrase\""
, "send " <> pass
, "expect eof"
, "EOF"
]
view (inproc "expect" [] expectArg)
where
-- Safely get the third item `cut` from the list.
getPwd xs = getPwd' 0 xs
getPwd' _ [] = ""
getPwd' n (x:xs) = if n == 2
then x
else getPwd' (n+1) xs
This function takes a tuple of (SSH key file name, string to search for in the text stored in the keyring), and pwds :: Shell Line
that is the entire contents of the keyring taken from a shell command.
The purpose of the function is to grep
the passwords, and call ssh-add
with the key filename and password.
The problem is this function doesn't type check:
sshkeys-autopass.hs:45:30: error:
• Couldn't match type ‘Text’ with ‘Line’
Expected type: Shell Line
Actual type: Shell Text
• In the third argument of ‘inproc’, namely ‘expectArg’
In the first argument of ‘view’, namely
‘(inproc "expect" [] expectArg)’
In a stmt of a 'do' block: view (inproc "expect" [] expectArg)
|
45 | view (inproc "expect" [] expectArg)
| ^^^^^^^^^
It seems like Shell Line
needs to become Shell Text
, how can this be done, please? I am open to the possibility this is structured badly or is not idiomatic Haskell (it does smell), if so please advise how this function could be better.
While I can't try out your code right now, it seems roundtripping your commands through Text
(as T.unlines
forces you to) is causing unnecessary trouble. According to the documentation (emphasis mine):
A
(Shell a)
is a protected stream ofa
's with side effects
As a Shell Line
is a stream, it can supply multiple Line
s. And sure enough, there is a function called select
...
select :: Foldable f => f a -> Shell a
... which will convert a list (or any other Foldable
) to a Shell
. You can use it to get the Shell Line
you need directly:
{-# LANGUAGE OverloadedStrings #-}
-- etc.
let expectArg = do
ml <- grep (contains (text str)) pwds
-- unsafeTextToLine is presumably safe here,
-- as ml was a Line to begin with.
let pass = unsafeTextToLine . getPwd $ cut tab (format l ml)
select [ "<< EOF"
, "spawn ssh-add"
, "expect \"Enter passphrase\""
, "send " <> pass
, "expect eof"
, "EOF"
]
view (inproc "expect" [] expectArg)
Side questions:
Not sure why
(text str)
is needed here, but it won't typecheck without it (despite OverloadedStrings).
The only thing OverloadedStrings
does automatically is handling string literals. It won't silently convert Text
values to other instances of IsString
. An alternative to using text
would be changing your signature so that the type of str
is Pattern Text
rather than Text
.
Safely get the third item
cut
from the list.
Here is one way of writing getPwd
without writing the recursive algorithm explicitly, using a few functions from Data.Maybe
:
getPwd = fromMaybe "" . listToMaybe . drop 2
You might also like the atDef
from the safe package:
getPwd xs = atDef "" xs 2
But that causes another problem:
inproc
does not want aShell (NonEmpty Line)
. I have no idea what to do about this.
NonEmpty
is a type for lists that are guaranteed to have at least one element. In your case, the lack of a sensible way of going from NonEmpty Line
to Line
(concatenating the elements or picking the first one, for instance, wouldn't help at all) was a signal that a change of approach was necessary.