{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}
-- |
-- Module       : System.Process.Lens.CmdSpec
-- Copyright 	: (c) 2019-2021 Emily Pillmore
-- License	: BSD
--
-- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>
-- Stability	: Experimental
-- Portability	: TypeFamilies
--
-- This module provides the associated optics and combinators
-- for working with 'CmdSpec' objects. 'CmdSpec' consists of two
-- cases: a Shell command, which is a command to execute naively in the shell,
-- and a Raw command which is a command path together with its arguments. As a result,
-- 'CmdSpec' has prisms into those two cases.
--
-- There is also a convenient 'Traversal' available for working with the arglist
-- of a Raw command, as well as associated 'Review's for each prism, and combinators
-- for working with arguments monoidally.
--
-- We provide classy variants for all useful prisms
--
module System.Process.Lens.CmdSpec
( -- * Traversals
  arguments
  -- * Prisms
, _ShellCommand
, _RawCommand
  -- * Classy Prisms
, AsShell(..)
, AsRaw(..)

  -- * Combinators
, arguing
, shellOf
, rawOf
) where

import Control.Lens
import System.Process


-- $setup
-- >>> import Control.Lens
-- >>> import System.Process
-- >>> :set -XTypeApplications
-- >>> :set -XRank2Types

-- ---------------------------------------------------------- --
-- Optics

-- | A prism into the 'ShellCommand' case of a 'CmdSpec'
--
-- Examples:
--
-- >>> _ShellCommand # "ls -l"
-- ShellCommand "ls -l"
--
-- >>> ShellCommand "ls -l" ^? _ShellCommand
-- Just "ls -l"
--
-- >>> RawCommand "/bin/ls" ["-l"] ^? _ShellCommand
-- Nothing
--
_ShellCommand :: Prism' CmdSpec String
_ShellCommand :: p String (f String) -> p CmdSpec (f CmdSpec)
_ShellCommand = (String -> CmdSpec)
-> (CmdSpec -> Maybe String) -> Prism CmdSpec CmdSpec String String
forall b s a. (b -> s) -> (s -> Maybe a) -> Prism s s a b
prism' String -> CmdSpec
ShellCommand ((CmdSpec -> Maybe String) -> Prism CmdSpec CmdSpec String String)
-> (CmdSpec -> Maybe String) -> Prism CmdSpec CmdSpec String String
forall a b. (a -> b) -> a -> b
$ \case
  ShellCommand String
s -> String -> Maybe String
forall a. a -> Maybe a
Just String
s
  CmdSpec
_ -> Maybe String
forall a. Maybe a
Nothing

-- | A prism into the 'RawCommand' case of a 'CmdSpec'
--
-- Examples:
--
-- >>> RawCommand "/bin/ls" ["-l"] ^? _RawCommand
-- Just ("/bin/ls",["-l"])
--
-- >>> RawCommand "/bin/ls" ["-l"] ^? _ShellCommand
-- Nothing
--
-- >>> RawCommand "/bin/ls" ["-l"] ^. _RawCommand . _1
-- "/bin/ls"
--
-- >>> RawCommand "/bin/ls" ["-l"] ^. _RawCommand . _2
-- ["-l"]
--
_RawCommand :: Prism' CmdSpec (FilePath, [String])
_RawCommand :: p (String, [String]) (f (String, [String]))
-> p CmdSpec (f CmdSpec)
_RawCommand = ((String, [String]) -> CmdSpec)
-> (CmdSpec -> Maybe (String, [String]))
-> Prism CmdSpec CmdSpec (String, [String]) (String, [String])
forall b s a. (b -> s) -> (s -> Maybe a) -> Prism s s a b
prism' ((String -> [String] -> CmdSpec) -> (String, [String]) -> CmdSpec
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry String -> [String] -> CmdSpec
RawCommand) ((CmdSpec -> Maybe (String, [String]))
 -> Prism CmdSpec CmdSpec (String, [String]) (String, [String]))
-> (CmdSpec -> Maybe (String, [String]))
-> Prism CmdSpec CmdSpec (String, [String]) (String, [String])
forall a b. (a -> b) -> a -> b
$ \case
  RawCommand String
fp [String]
s -> (String, [String]) -> Maybe (String, [String])
forall a. a -> Maybe a
Just (String
fp, [String]
s)
  CmdSpec
_ -> Maybe (String, [String])
forall a. Maybe a
Nothing

-- | Classy prism into the shell command of a 'CmdSpec'
--
-- Examples:
--
-- >>> f :: AsShell a => a -> Maybe String; f = preview _Shell
-- >>> f $ _ShellCommand # "ls -l"
-- Just "ls -l"
--
class AsShell a where
  _Shell :: Prism' a String
  {-# MINIMAL _Shell #-}

instance AsShell CmdSpec where
  _Shell :: p String (f String) -> p CmdSpec (f CmdSpec)
_Shell = p String (f String) -> p CmdSpec (f CmdSpec)
Prism CmdSpec CmdSpec String String
_ShellCommand

-- | Classy prism into the raw command of a 'CmdSpec'
--
-- Examples:
--
-- >>> f :: AsRaw a => a -> Maybe FilePath; f = preview (_Raw . _1)
-- >>> f $ _RawCommand # ("/bin/ls", ["ls -l"])
-- Just "/bin/ls"
--
class AsRaw a where
  _Raw :: Prism' a (FilePath, [String])
  {-# MINIMAL _Raw #-}

instance AsRaw CmdSpec where
  _Raw :: p (String, [String]) (f (String, [String]))
-> p CmdSpec (f CmdSpec)
_Raw = p (String, [String]) (f (String, [String]))
-> p CmdSpec (f CmdSpec)
Prism CmdSpec CmdSpec (String, [String]) (String, [String])
_RawCommand

-- | 'Traversal'' into the arguments of a command
--
-- Examples:
--
-- >>> RawCommand "/bin/ls" ["-l"] ^. arguments
-- ["-l"]
--
arguments :: AsRaw a => Traversal' a [String]
arguments :: Traversal' a [String]
arguments = ((String, [String]) -> f (String, [String])) -> a -> f a
forall a. AsRaw a => Prism' a (String, [String])
_Raw (((String, [String]) -> f (String, [String])) -> a -> f a)
-> (([String] -> f [String])
    -> (String, [String]) -> f (String, [String]))
-> ([String] -> f [String])
-> a
-> f a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([String] -> f [String])
-> (String, [String]) -> f (String, [String])
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
traverse

-- ---------------------------------------------------------- --
-- Combinators

-- | Append an argument to the argument list of a 'RawCommand'
--
-- Examples:
--
-- >>> arguing "-h" $ RawCommand "/bin/ls" ["-l"]
-- RawCommand "/bin/ls" ["-l","-h"]
--
-- >>> arguing "-h" (RawCommand "/bin/ls" ["-l"]) ^. arguments
-- ["-l","-h"]
--
arguing :: AsRaw a => String -> a -> a
arguing :: String -> a -> a
arguing String
s = ([String] -> Identity [String]) -> a -> Identity a
forall a. AsRaw a => Traversal' a [String]
arguments (([String] -> Identity [String]) -> a -> Identity a)
-> [String] -> a -> a
forall a s t. Semigroup a => ASetter s t a a -> a -> s -> t
<>~ [String
s]

-- | Lift a 'String' into a type via 'ShellCommand' with a prism into the
--
-- Examples:
--
-- >>> shellOf @CmdSpec "ls"
-- ShellCommand "ls"
--
shellOf :: AsShell a => String -> a
shellOf :: String -> a
shellOf = AReview a String -> String -> a
forall b (m :: * -> *) t. MonadReader b m => AReview t b -> m t
review AReview a String
forall a. AsShell a => Prism' a String
_Shell

-- | Lift a 'FilePath' and list of arguments into a type via 'RawCommand'
-- with a prism into the command
--
-- Examples:
--
-- >>> rawOf @CmdSpec "/bin/ls" ["-l"]
-- RawCommand "/bin/ls" ["-l"]
--
rawOf :: AsRaw a => FilePath -> [String] -> a
rawOf :: String -> [String] -> a
rawOf String
fp [String]
ss = Tagged (String, [String]) (Identity (String, [String]))
-> Tagged a (Identity a)
forall a. AsRaw a => Prism' a (String, [String])
_Raw (Tagged (String, [String]) (Identity (String, [String]))
 -> Tagged a (Identity a))
-> (String, [String]) -> a
forall t b. AReview t b -> b -> t
# (String
fp, [String]
ss)