{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, 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.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' = unwords $ map (compile . cbra) p ns' = unwords $ 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 = call (name n) 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 {-# OVERLAPPABLE #-} 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