packages feed

headroom-0.1.3.0: src/Headroom/Types.hs

{-|
Module      : Headroom.Types
Description : Data types and instances
Copyright   : (c) 2019-2020 Vaclav Svejcar
License     : BSD-3
Maintainer  : vaclav.svejcar@gmail.com
Stability   : experimental
Portability : POSIX

Data types and type class instances shared between modules.
-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Headroom.Types
  ( AppConfigError(..)
  , HeadroomError(..)
  , NewLine(..)
  , RunMode(..)
  , InitCommandError(..)
  )
where

import           Data.Aeson                     ( FromJSON(parseJSON)
                                                , ToJSON(toJSON)
                                                , Value(String)
                                                )
import           RIO
import qualified RIO.List                      as L
import qualified RIO.Text                      as T


-- | Error occured during validation of application configuration.
data AppConfigError
  = EmptySourcePaths       -- ^ no paths to source code files provided
  | EmptyTemplatePaths     -- ^ no paths to license header templates provided
  deriving (Show)

-- | Represents fatal application error, that should be displayed to user in
-- some human readable form.
data HeadroomError
  = InvalidAppConfig [AppConfigError] -- ^ invalid application configuration
  | InvalidLicense Text               -- ^ unknown license is selected in /Generator/
  | InvalidVariable Text              -- ^ invalid variable format (@key=value@)
  | MissingVariables Text [Text]      -- ^ not all variables were filled in template
  | NoGenModeSelected                 -- ^ no mode for /Generator/ command is selected
  | ParseError Text                   -- ^ error parsing template file
  | InitCommandError InitCommandError -- ^ error during execution of /Init/ command
  deriving (Show, Typeable)

-- | Errors specific for the /Init/ command.
data InitCommandError
  = AppConfigAlreadyExists  -- ^ application configuration file already exists
  | InvalidLicenseType Text -- ^ invalid license type specified
  | NoSourcePaths           -- ^ no paths to source code files provided
  | NoSupportedFileType     -- ^ no supported file types found on source paths
  deriving (Show)

-- | Represents newline separator.
data NewLine
  = CR   -- ^ line ends with @\r@
  | CRLF -- ^ line ends with @\r\n@
  | LF   -- ^ line ends with @\n@
  deriving (Eq, Show)

-- | Mode of the /Run/ command, states how to license headers in source code
-- files.
data RunMode
  = Add     -- ^ add license header if missing in source code file
  | Drop    -- ^ drop any license header if present in source code file
  | Replace -- ^ replace existing or add license header
  deriving (Eq, Show)

displayAppConfigError :: AppConfigError -> Text
displayAppConfigError = \case
  EmptySourcePaths   -> "no paths to source code files"
  EmptyTemplatePaths -> "no paths to template files"

----------------------------  TYPE CLASS INSTANCES  ----------------------------

instance Exception HeadroomError where
  displayException = \case
    InvalidAppConfig errors -> mconcat
      [ "Invalid configuration, following problems found:\n"
      , L.intercalate
        "\n"
        (fmap (\e -> "\t- " <> (T.unpack . displayAppConfigError $ e)) errors)
      ]
    InvalidLicense raw -> "Cannot parse license type from: " <> T.unpack raw
    InvalidVariable raw ->
      "Cannot parse variable key=value from: " <> T.unpack raw
    NoGenModeSelected
      -> "Please select at least one option what to generate (see --help for details)"
    MissingVariables name variables -> mconcat
      ["Missing variables for template '", T.unpack name, "': ", show variables]
    ParseError       msg     -> "Error parsing template: " <> T.unpack msg
    InitCommandError icError -> case icError of
      AppConfigAlreadyExists -> "Config file '.headroom.yaml' already exists"
      InvalidLicenseType raw -> "Invalid license type: " <> T.unpack raw
      NoSourcePaths          -> "No path to source code files defined"
      NoSupportedFileType    -> "No supported file type found"

instance FromJSON RunMode where
  parseJSON (String s) = case T.toLower s of
    "add"     -> pure Add
    "drop"    -> pure Drop
    "replace" -> pure Replace
    _         -> error $ "Unknown run mode: " <> T.unpack s
  parseJSON other = error $ "Invalid value for run mode: " <> show other

instance ToJSON RunMode where
  toJSON = \case
    Add     -> "add"
    Drop    -> "drop"
    Replace -> "replace"