module Stackctl.FilterOption
  ( FilterOption
  , defaultFilterOption
  , HasFilterOption (..)
  , envFilterOption
  , filterOption
  , filterOptionFromPaths
  , filterOptionFromText
  , filterOptionToPaths
  , filterStackSpecs
  ) where

import Stackctl.Prelude

import qualified Data.List.NonEmpty as NE
import Data.Semigroup (Last (..))
import qualified Data.Text as T
import qualified Env
import Options.Applicative
import Stackctl.AWS.CloudFormation (StackName (..))
import Stackctl.StackSpec
import System.FilePath (hasExtension)
import System.FilePath.Glob

newtype FilterOption = FilterOption
  { FilterOption -> NonEmpty Pattern
unFilterOption :: NonEmpty Pattern
  }
  deriving (NonEmpty FilterOption -> FilterOption
FilterOption -> FilterOption -> FilterOption
forall b. Integral b => b -> FilterOption -> FilterOption
forall a.
(a -> a -> a)
-> (NonEmpty a -> a)
-> (forall b. Integral b => b -> a -> a)
-> Semigroup a
stimes :: forall b. Integral b => b -> FilterOption -> FilterOption
$cstimes :: forall b. Integral b => b -> FilterOption -> FilterOption
sconcat :: NonEmpty FilterOption -> FilterOption
$csconcat :: NonEmpty FilterOption -> FilterOption
<> :: FilterOption -> FilterOption -> FilterOption
$c<> :: FilterOption -> FilterOption -> FilterOption
Semigroup) via Last FilterOption

instance ToJSON FilterOption where
  toJSON :: FilterOption -> Value
toJSON = forall a. ToJSON a => a -> Value
toJSON forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilterOption -> String
showFilterOption
  toEncoding :: FilterOption -> Encoding
toEncoding = forall a. ToJSON a => a -> Encoding
toEncoding forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilterOption -> String
showFilterOption

class HasFilterOption env where
  filterOptionL :: Lens' env FilterOption

instance HasFilterOption FilterOption where
  filterOptionL :: Lens' FilterOption FilterOption
filterOptionL = forall a. a -> a
id

envFilterOption :: String -> Env.Parser Env.Error FilterOption
envFilterOption :: String -> Parser Error FilterOption
envFilterOption String
items = String -> Parser Error FilterOption
var String
"FILTERS" forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> String -> Parser Error FilterOption
var String
"FILTER"
 where
  var :: String -> Parser Error FilterOption
var String
name =
    forall e a.
AsUnset e =>
Reader e a -> String -> Mod Var a -> Parser e a
Env.var (forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first String -> Error
Env.UnreadError forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Either String FilterOption
readFilterOption) String
name
      forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. HasHelp t => String -> Mod t a
Env.help
      forall a b. (a -> b) -> a -> b
$ String
"Filter "
      forall a. Semigroup a => a -> a -> a
<> String
items
      forall a. Semigroup a => a -> a -> a
<> String
" by patterns"

filterOption :: String -> Parser FilterOption
filterOption :: String -> Parser FilterOption
filterOption String
items =
  forall a. ReadM a -> Mod OptionFields a -> Parser a
option (forall a. (String -> Either String a) -> ReadM a
eitherReader String -> Either String FilterOption
readFilterOption)
    forall a b. (a -> b) -> a -> b
$ forall a. Monoid a => [a] -> a
mconcat
      [ forall (f :: * -> *) a. HasName f => String -> Mod f a
long String
"filter"
      , forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
metavar String
"PATTERN[,PATTERN]"
      , forall (f :: * -> *) a. String -> Mod f a
help forall a b. (a -> b) -> a -> b
$ String
"Filter " forall a. Semigroup a => a -> a -> a
<> String
items forall a. Semigroup a => a -> a -> a
<> String
" to match PATTERN(s)"
      ]

filterOptionFromPaths :: NonEmpty FilePath -> FilterOption
filterOptionFromPaths :: NonEmpty String -> FilterOption
filterOptionFromPaths = NonEmpty Pattern -> FilterOption
FilterOption forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> Pattern
compile

filterOptionFromText :: Text -> Maybe FilterOption
filterOptionFromText :: Text -> Maybe FilterOption
filterOptionFromText =
  forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap NonEmpty Pattern -> FilterOption
FilterOption
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> Maybe (NonEmpty a)
NE.nonEmpty
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Text -> [Pattern]
expandPatterns
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
T.strip
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> [Text]
T.splitOn Text
","

expandPatterns :: Text -> [Pattern]
expandPatterns :: Text -> [Pattern]
expandPatterns Text
t = forall a b. (a -> b) -> [a] -> [b]
map String -> Pattern
compile forall a b. (a -> b) -> a -> b
$ String
s forall a. a -> [a] -> [a]
: [String]
expanded
 where
  expanded :: [String]
expanded
    | Text
"**" Text -> Text -> Bool
`T.isPrefixOf` Text
t = [String]
suffixed
    | Bool
otherwise = forall a b. (a -> b) -> [a] -> [b]
map (String
"**" String -> String -> String
</>) forall a b. (a -> b) -> a -> b
$ String
s forall a. a -> [a] -> [a]
: [String]
suffixed

  suffixed :: [String]
suffixed
    | Text
"*" Text -> Text -> Bool
`T.isSuffixOf` Text
t Bool -> Bool -> Bool
|| String -> Bool
hasExtension String
s = []
    | Bool
otherwise = (String
s String -> String -> String
</> String
"**" String -> String -> String
</> String
"*") forall a. a -> [a] -> [a]
: forall a b. (a -> b) -> [a] -> [b]
map (String
s String -> String -> String
<.>) forall {a}. IsString a => [a]
extensions

  extensions :: [a]
extensions = [a
"json", a
"yaml"]

  s :: String
s = Text -> String
unpack Text
t

readFilterOption :: String -> Either String FilterOption
readFilterOption :: String -> Either String FilterOption
readFilterOption = forall a b. a -> Maybe b -> Either a b
note forall {a}. IsString a => a
err forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Maybe FilterOption
filterOptionFromText forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
pack
 where
  err :: a
err = a
"Must be non-empty, comma-separated list of non-empty patterns"

showFilterOption :: FilterOption -> String
showFilterOption :: FilterOption -> String
showFilterOption =
  Text -> String
unpack
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text] -> Text
T.intercalate Text
","
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (String -> Text
pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. Pattern -> String
decompile)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. NonEmpty a -> [a]
NE.toList
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilterOption -> NonEmpty Pattern
unFilterOption

defaultFilterOption :: FilterOption
defaultFilterOption :: FilterOption
defaultFilterOption = NonEmpty String -> FilterOption
filterOptionFromPaths forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Applicative f => a -> f a
pure String
"**/*"

filterOptionToPaths :: FilterOption -> [FilePath]
filterOptionToPaths :: FilterOption -> [String]
filterOptionToPaths = forall a b. (a -> b) -> [a] -> [b]
map Pattern -> String
decompile forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. NonEmpty a -> [a]
NE.toList forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilterOption -> NonEmpty Pattern
unFilterOption

filterStackSpecs :: FilterOption -> [StackSpec] -> [StackSpec]
filterStackSpecs :: FilterOption -> [StackSpec] -> [StackSpec]
filterStackSpecs FilterOption
fo =
  forall a. (a -> Bool) -> [a] -> [a]
filter forall a b. (a -> b) -> a -> b
$ \StackSpec
spec -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Pattern -> StackSpec -> Bool
`matchStackSpec` StackSpec
spec) forall a b. (a -> b) -> a -> b
$ FilterOption -> NonEmpty Pattern
unFilterOption FilterOption
fo

matchStackSpec :: Pattern -> StackSpec -> Bool
matchStackSpec :: Pattern -> StackSpec -> Bool
matchStackSpec Pattern
p StackSpec
spec =
  forall (t :: * -> *). Foldable t => t Bool -> Bool
or
    [ Pattern -> String -> Bool
match Pattern
p forall a b. (a -> b) -> a -> b
$ Text -> String
unpack forall a b. (a -> b) -> a -> b
$ StackName -> Text
unStackName forall a b. (a -> b) -> a -> b
$ StackSpec -> StackName
stackSpecStackName StackSpec
spec
    , Pattern -> String -> Bool
match Pattern
p forall a b. (a -> b) -> a -> b
$ StackSpec -> String
stackSpecStackFile StackSpec
spec
    , Pattern -> String -> Bool
match Pattern
p forall a b. (a -> b) -> a -> b
$ StackSpec -> String
stackSpecTemplateFile StackSpec
spec
    ]