{-# LANGUAGE LambdaCase, ScopedTypeVariables #-} module Workflow.Pure.Execute where import Workflow.Types import Control.Monad.Free -- import Control.Monad.Trans.Free hiding (Pure, Free, iterM) -- TODO import Control.Monad import Control.Monad.Identity import Control.Monad.Trans.State import Control.Monad.Trans.Writer import Control.Monad.Trans.Maybe import Data.Foldable (traverse_) import Data.List (intercalate) import Data.Monoid ((<>)) import Data.Function ((&)) {-| downcasts a monad to a list of functors. e.g. >>> isSimpleWorkflow $ getClipboard >>= sendText Nothing >>> isSimpleWorkflow $ setClipboard "copying..." >> sendKeyChord [HyperModifier] CKey Just [SetClipboard "copying..." (),SendKeyChord [HyperModifier] CKey ()] TODO When 'Just': @ 'isSimpleWorkflow' >>> fromJust >>> 'fromWorkflow_' === 'id' @ -} isSimpleWorkflow :: Workflow x -> Maybe [Workflow_] isSimpleWorkflow m = (runIdentity . runMaybeT . execWriterT) (goM m) where log = tell . (:[]) goM :: Workflow x -> SimpleWorkflowM () goM = \case Pure _ -> return () Free a -> goF a goF :: WorkflowF (Workflow x) -> SimpleWorkflowM () goF = \case -- simple SendKeyChord flags key k -> log (SendKeyChord_ flags key) >> goM k SendText s k -> log (SendText_ s ) >> goM k SendMouseClick flags n button k -> log (SendMouseClick_ flags n button) >> goM k SendMouseScroll flags scroll n k -> log (SendMouseScroll_ flags scroll n) >> goM k SetClipboard s k -> log (SetClipboard_ s ) >> goM k OpenApplication app k -> log (OpenApplication_ app) >> goM k OpenURL url k -> log (OpenURL_ url) >> goM k Delay t k -> log (Delay_ t ) >> goM k -- complex GetClipboard _ -> mzero CurrentApplication _ -> mzero type SimpleWorkflowM = WriterT [Workflow_] (MaybeT Identity) {- | shows (an inaccurate approximation of) the "static" data flow of some 'Workflow', by showing its primitive operations (in @do-notation@). e.g. >>> :{ putStrLn . showWorkflow $ do sendKeyChord [Command, Shift] BKey delay 1000 sendKeyChord [Command] DownArrowKey x1 <- currentApplication x2 <- getClipboard openURL $ "https://www.google.com/search?q=" <> x2 setClipboard x1 getClipboard :} do sendKeyChord ([Command,Shift]) (BKey) delay (1000) sendKeyChord ([Command]) (DownArrowKey) x1 <- currentApplication x2 <- getClipboard openURL ("https://www.google.com/search?q={x2}") setClipboard ("{x1}") x3 <- getClipboard return "{x3}" (note: doesn't print variables as raw strings (cf. 'print' versus 'putStrLn'), as it doesn't "crystallize" all operations into "symbols", but gives you an idea of the data flow. however, it does correctly track the control flow, even when the variables are used non-sequentially.) (note: the variables in the code were named to be consistent with 'gensym', for readability. but of course the bindings aren't reified, and they could have been named anything) basically, the monadically-bound variable @x1@ is shown as if it were literally @"{x1}"@ (rather than, the current clipboard contents). a more complicated alternative could be to purely model the state: e.g. a clipboard, with 'SetClipboard' and 'GetClipboard' working together, etc.). TODO would be complicated by SendTextTo; unless unit consuctors are distinguished (as sendTextTo =<< currentApplication is kinda Applicative). -} showWorkflow :: (Show x) => Workflow x -> String showWorkflow m = "do\n" <> (evalState&flip) 1 (showWorkflow_ m) where showWorkflow_ :: (Show x) => Workflow x -> GensymM String showWorkflow_ = \case Pure x -> return $ " return " <> show x <> "\n" Free a -> showWorkflowF a showWorkflowF :: (Show x) => WorkflowF (Workflow x) -> GensymM String showWorkflowF = \case SendKeyChord flags key k -> ((" sendKeyChord " <> showArgs [show flags, show key]) <>) <$> showWorkflow_ k -- TODO SendMouseClick flags n b k -> ((" sendMouseClick " <> showArgs [show flags, show n, show b]) <>) <$> showWorkflow_ k SendText s k -> ((" sendText " <> showArgs [show s]) <>) <$> showWorkflow_ k SendMouseClick flags n button k -> ((" sendMouseClick " <> showArgs [show flags, show n, show button]) <>) <$> showWorkflow_ k SendMouseScroll flags scroll n k -> ((" sendMouseScroll " <> showArgs [show flags, show scroll, show n]) <>) <$> showWorkflow_ k SetClipboard s k -> ((" setClipboard " <> showArgs [show s]) <>) <$> showWorkflow_ k OpenApplication app k -> ((" openApplication " <> showArgs [show app]) <>) <$> showWorkflow_ k OpenURL url k -> ((" openURL " <> showArgs [show url]) <>) <$> showWorkflow_ k Delay t k -> ((" delay " <> showArgs [show t]) <>) <$> showWorkflow_ k -- TODO distinguish between strings and variables to avoid: -- x2 <- getClipboard -- sendText ("x2") GetClipboard f -> do x <- gensym rest <- showWorkflow_ (f ("{"<>x<>"}")) return $ " " <> x <> " <- getClipboard" <> showArgs [] <> rest CurrentApplication f -> do x <- gensym rest <- showWorkflow_ (f ("{"<>x<>"}")) return $ " " <> x <> " <- currentApplication" <> showArgs [] <> rest showArgs :: [String] -> String showArgs xs = intercalate " " (fmap (("(" <>) . (<> ")")) xs) <> "\n" gensym :: State Int String gensym = do i <- get put $ i + 1 return $ "x" <> show i type GensymM = State Gensym type Gensym = Int {- \case SendKeyChord flags key k -> k SendText s k -> k SendMouseClick flags n button k -> k SendMouseScroll flags scroll n k -> k SetClipboard s k -> k OpenApplication app k -> k OpenURL url k -> k Delay t k -> k GetClipboard f -> f CurrentApplication f -> f -}