packages feed

hsdev-0.1.2.1: src/System/Win32/PowerShell.hs

{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverlappingInstances, GeneralizedNewtypeDeriving #-}

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, (.=), flag, named, call, cmdlet, lambda, (.|), foreach, filter,
	-- * Convertible
	ToPS(..),
	-- * Escape functions
	translate, translateArg
	) 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 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 . bra) es)
compile (Invoke (CmdLet e (Args p ns))) = unwords [invoke', p', ns'] where
	invoke' = case e of
		Var n -> n
		_ -> unwords ["&", compile $ bra e]
	p' = intercalate " " $ map (compile . bra) p
	ns' = intercalate " " $ concatMap named' $ M.toList ns where
		named' :: (String, Maybe Expr) -> [String]
		named' (n, Just v) = [n, compile $ bra 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

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 = '"' : 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 ++ "'"