packages feed

shellify-0.13.0.0: src/Options.hs

module Options (Package(..), Options(..), OutputForm(..), def, Packages(Packages), options) where
import Prelude hiding (takeWhile, writeFile)

import Constants
import FlakeTemplate
import ShellifyTemplate

import Control.Applicative ((<|>))
import Control.Arrow ((+++))
import Control.Monad (when)
import Data.Bool (bool)
import Data.Default (Default(def))
import Data.List (find, sort)
import Data.Maybe (fromMaybe)
import Data.Text (isInfixOf, isPrefixOf, pack, replace, splitOn, stripPrefix, takeWhile, Text(), unpack)
import Data.Text.IO (hPutStrLn, writeFile)
import Data.Version (showVersion)
import qualified Data.Text.IO as Text
import GHC.IO.Exception (ExitCode(ExitSuccess, ExitFailure))
import Paths_shellify (version)
import System.Directory (doesPathExist)
import System.Exit (exitWith)
import System.IO (stderr)
import Text.StringTemplate (newSTMP, render, setAttribute, StringTemplate)

data OutputForm = Traditional
                | Flake
     deriving (Eq, Show)

newtype Packages = Packages [ Package ] deriving Show


type Package = Text

instance Eq Packages where
  Packages a == Packages b = sort a == sort b

packageList = toPackageList . packages
  where toPackageList (Packages p) = p

data Options = Options {
    packages :: Packages
  , command :: Maybe Text
  , outputForm :: !OutputForm
  , prioritiseLocalPinnedSystem :: Bool
} deriving (Eq, Show)

data OptionsParser = OptionsParser [Text] -- remainingOptions
                                   (Either Text (Options -> Options)) -- result

options :: Text -> [Text] -> Either Text Options
options progName args =
  let optionsHandler | hasShellArg args = newStyleOption
                     | otherwise = oldStyleOption
      shellArgFilter | hasShellArg args = filter (/= "shell")
                     | otherwise = id
      optionsCaller f = worker 
       where worker (OptionsParser [] t) = t
             worker (OptionsParser (hd:tl) res) =
               let (OptionsParser newRemaining newRes) = f hd tl
               in worker $ OptionsParser newRemaining ((.) <$> newRes <*> res)

      screenForNoPackages (Right opts) | null (packageList opts) = Left noPackagesError
      screenForNoPackages anyThingElse = anyThingElse
      initialArgumentsToParse = shellArgFilter args
      initialModifier = Right $ if hasShellArg args then setFlakeGeneration else id
      initialOptionParser = OptionsParser initialArgumentsToParse initialModifier
  in screenForNoPackages $ ($ def) <$> optionsCaller optionsHandler initialOptionParser

  where oldStyleOption :: Text -> [Text] -> OptionsParser
        oldStyleOption "-p" = handlePackageSwitch
        oldStyleOption "--packages" = handlePackageSwitch
        oldStyleOption opt = baseOption opt
        newStyleOption "-p" = returnError "-p not supported with new style commands"
        newStyleOption "--packages" = returnError "--packages not supported with new style commands"
        newStyleOption "--allow-local-pinned-registries-to-be-prioritized" = transformOptionsWith setPrioritiseLocalPinnedSystem
        newStyleOption arg | isSwitch arg = baseOption arg
                           | otherwise = transformOptionsWith $ appendPackages [arg]
        baseOption :: Text -> [Text] -> OptionsParser
        baseOption "-h" = returnError $ helpText progName
        baseOption "--help" = returnError $ helpText progName
        baseOption "--version" = returnError $ "Shellify " <> pack ( showVersion version)
        baseOption "--command" = handleCommandSwitch
        baseOption "--run" = handleCommandSwitch
        baseOption "--with-flake" = transformOptionsWith setFlakeGeneration
        baseOption _ = transformOptionsWith id
        --doNothing = transformOptionsWith id
        transformOptionsWith fun wds = OptionsParser wds (Right fun)
        handlePackageSwitch wds = let (pkgs, remainingOptions) = consumePackageArgs wds
                                  in transformOptionsWith (appendPackages pkgs) remainingOptions
        handleCommandSwitch (hd:tl) | isSwitch hd
                                    = returnError "Argument missing to switch" tl
                                    | otherwise
                                    = transformOptionsWith (setCommand hd) tl
        handleCommandSwitch [] = returnError "Argument missing to switch" []

        appendPackages ps opts = opts{
          packages = Packages (ps ++ packageList opts)
        }
        setCommand cmd opts = opts{command=Just cmd}
        setFlakeGeneration opts = opts{outputForm=Flake}
        setPrioritiseLocalPinnedSystem opts = opts {prioritiseLocalPinnedSystem=True}
        returnError errorText remaining = OptionsParser remaining $ Left errorText

consumePackageArgs :: [Text] -> ([Package], [Text])
consumePackageArgs = worker []
  where worker pkgs [] = (pkgs, [])
        worker pkgs options@(hd:_) | isSwitch hd
                                   = (pkgs, options)
        worker pkgs (hd:tl) = worker (hd:pkgs) tl

hasShellArg [] = False
hasShellArg ("shell":_) = True
hasShellArg (hd:tl) | isSwitch hd = hasShellArg tl
                    | otherwise = False

isSwitch = isPrefixOf "-"

instance Default Options where
  def = Options (Packages []) Nothing Traditional False