hsdev-0.1.3.0: src/System/Win32/PowerShell.hs
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverlappingInstances, GeneralizedNewtypeDeriving, LambdaCase #-}
module System.Win32.PowerShell (
-- * Run PowerShell
ps,
-- * PowerShell functions
startProcess, codepage, outNull,
-- * PowerShell monad
PS(..), seqPS, emit, emit_, (=:), invoke,
-- * Expression
Args(..), CmdLet(..), Expr(..), compile,
raw, name, lit, var, bra, cbra, (.=), flag, named, call, cmdlet, lambda, (.|), foreach, filter,
-- * Convertible
ToPS(..),
-- * Escape functions
translate, translateArg,
quote, quoteDouble,
escape
) where
import Prelude hiding (filter)
import Control.Applicative (Applicative(..))
import Control.Monad
import Control.Monad.Writer
import Data.Char (isAlphaNum)
import Data.List (intercalate)
import qualified Data.List as List (filter)
import Data.Map (Map)
import qualified Data.Map as M
import HsDev.Tools.Base (ToolM, tool_)
ps :: PS a -> ToolM String
ps script = tool_ "powershell" ["-Command", compile $ seqPS script]
-- start-process
startProcess :: String -> [String] -> CmdLet
startProcess n as = cmdlet "start-process" [name n, lit $ intercalate ", " (map translateArg as)] [named "WindowStyle" $ lit "Hidden"]
-- chcp
codepage :: Int -> CmdLet
codepage n = cmdlet "chcp" [lit n] []
-- out-null
outNull :: CmdLet
outNull = cmdlet "out-null" [] []
newtype PS a = PS { unPS :: Writer [Expr] a }
deriving (Applicative, Functor, Monad, MonadWriter [Expr])
seqPS :: PS a -> Expr
seqPS (PS act) = case execWriter act of
[] -> error "No expressions"
es -> foldr1 Sequence es
emit :: Expr -> PS Expr
emit e = PS $ tell [e] >> return e
emit_ :: Expr -> PS ()
emit_ = void . emit
infixr 6 =:
(=:) :: String -> Expr -> PS Expr
n =: expr = emit_ (n .= expr) >> return (var n)
-- | Invoke cmdlet
invoke :: CmdLet -> PS ()
invoke = emit_ . Invoke
-- | Positional and named args
data Args = Args [Expr] (Map String (Maybe Expr))
instance Monoid Args where
mempty = Args [] M.empty
mappend (Args lp ln) (Args rp rn) = Args (lp ++ rp) (M.union ln rn)
-- | Call to cmdlet
data CmdLet = CmdLet {
cmdLet :: Expr,
cmdArgs :: Args }
-- | Expression
data Expr =
Emit String |
-- ^ native expression
Literal String |
-- ^ literal
Var String |
-- ^ $name
Bracket Expr |
-- ^ (expr)
Assign [String] [Expr] |
-- ^ $x, $y, ... = expr1, expr2, ...
Invoke CmdLet |
-- ^ cmd args... named...
Lambda [String] ([Expr] -> Expr) |
-- ^ { param($...); expr }
Sequence Expr Expr |
-- ^ expr1; expr2
Pipe Expr CmdLet
-- ^ expr | cmd
compile :: Expr -> String
compile (Emit s) = s
compile (Literal l) = l
compile (Var v) = '$':v
compile (Bracket e) = "(" ++ compile e ++ ")"
compile (Assign vs es) = intercalate ", " (map ('$':) vs) ++ " = " ++ intercalate ", " (map (compile . cbra) es)
compile (Invoke (CmdLet e (Args p ns))) = unwords $ List.filter (not . null) [invoke', p', ns'] where
invoke' = case e of
Var n -> n
_ -> unwords ["&", compile $ cbra e]
p' = intercalate " " $ map (compile . cbra) p
ns' = intercalate " " $ concatMap named' $ M.toList ns where
named' :: (String, Maybe Expr) -> [String]
named' (n, Just v) = ['-':n, compile $ cbra v]
named' (n, Nothing) = ['-':n]
-- { param($File); $args[0]; }
compile (Lambda ns body) = unwords ["{", param', body', "}"] where
param' = "param(" ++ intercalate "," ns ++ ");"
body' = compile $ body (map var ns)
compile (Sequence l r) = compile l ++ "; " ++ compile r
compile (Pipe e c) = compile e ++ " | " ++ compile (Invoke c)
raw :: String -> Expr
raw = Emit
name :: String -> Expr
name = Literal
lit :: ToPS a => a -> Expr
lit = Literal . toPS
var :: String -> Expr
var = Var
bra :: Expr -> Expr
bra = Bracket
cbra :: Expr -> Expr
cbra e@(Literal _) = e
cbra v@(Var _) = v
cbra b = bra b
infixr 6 .=
(.=) :: String -> Expr -> Expr
v .= e = Assign [v] [e]
flag :: String -> (String, Maybe Expr)
flag n = (n, Nothing)
named :: String -> Expr -> (String, Maybe Expr)
named n e = (n, Just e)
call :: Expr -> [Expr] -> [(String, Maybe Expr)] -> CmdLet
call f pos named' = CmdLet f $ Args pos (M.fromList named')
cmdlet :: String -> [Expr] -> [(String, Maybe Expr)] -> CmdLet
cmdlet n pos = call (name n) pos
lambda :: [String] -> ([Expr] -> Expr) -> Expr
lambda = Lambda
infixr 6 .|
(.|) :: Expr -> CmdLet -> Expr
e .| c = Pipe e c
foreach :: (Expr -> Expr) -> CmdLet
foreach f = cmdlet "%" [f $ var "_"] []
filter :: (Expr -> Expr) -> CmdLet
filter p = cmdlet "?" [p $ var "_"] []
class ToPS a where
toPS :: a -> String
instance ToPS Int where
toPS = show
instance ToPS String where
toPS = translate
instance ToPS Bool where
toPS True = "$true"
toPS False = "$false"
instance ToPS a => ToPS [a] where
toPS = intercalate ", " . map toPS
translate :: String -> String
translate s
| all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s
| otherwise = '"' : snd (foldr escape' (True, "\"") s)
where
escape' '"' (_, s') = (True, '\\' : '"' : s')
escape' '\\' (True, s') = (True, '\\' : '\\' : s')
escape' '\\' (False, s') = (False, '\\' : s')
escape' c (_, s') = (False, c : s')
translateArg :: String -> String
translateArg s
| all isAlphaNum s = s
| otherwise = "'" ++ translate s ++ "'"
quote :: String -> String
quote s = "'" ++ concatMap (\case { '\'' -> "''"; ch -> [ch] }) s ++ "'"
quoteDouble :: String -> String
quoteDouble s = "\"" ++ concatMap (\case { '"' -> "\"\""; ch -> [ch] }) s ++ "\""
escape :: (String -> String) -> String -> String
escape fn s
| all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s
| otherwise = fn s