{-|
Module      : Hoyo.Command
Copyright   : (c) Frederick Pringle, 2023
License     : BSD-3-Clause
Maintainer  : freddyjepringle@gmail.com

This module defines data-types and runner functions for the hoyo
command-line program.
-}

module Hoyo.Command (
  -- * Running CLI commands
  runCommand
  , modifyBookmarks
  , modifyBookmarksM

  -- ** Specific command runners
  , runAdd
  , runMove
  , runList
  , runClear
  , runDelete
  , runRefresh
  , runConfig
  , runCheck
  , runDefaultCommand

  -- * Types
  , Options (..)
  , Command (..)
  , AddOptions (..)
  , MoveOptions (..)
  , ListOptions (..)
  , ClearOptions (..)
  , DeleteOptions (..)
  , RefreshOptions (..)
  , ConfigPrintOptions (..)
  , ConfigResetOptions (..)
  , ConfigSetOptions (..)
  , ConfigAddDefaultOptions (..)
  , ConfigCommand (..)
  , CheckOptions (..)
  , HelpOptions (..)
  , GlobalOptions (..)
  , defaultGlobalOptions
  , OverrideOptions (..)
  , defaultOverrideOptions
  , overrideConfig
  , overrideEnv
  , verifyOverrides
  , combOverride
  , MaybeOverride (..)
  ) where

{- HLINT ignore "reduce duplication -}

import                          Control.Applicative
import                          Control.Exception          (bracket_)
import                          Control.Monad
import                          Control.Monad.Except       (throwError)
import                          Control.Monad.IO.Class
import                          Control.Monad.Reader.Class (ask)

import                          Data.Bifunctor
import                          Data.Char                  (isDigit)
import                          Data.Function
import                          Data.List
import                qualified Data.Text                  as T
import                          Data.Time

import                          Hoyo.Bookmark
import {-# SOURCE #-}           Hoyo.CLI.Parse
import                          Hoyo.Config
import                          Hoyo.Internal.Types
import                          Hoyo.Utils

import                          Lens.Micro
import                          Lens.Micro.Extras

import                          System.Console.ANSI        hiding (Reset)
import                          System.Directory
import                          System.Exit
import                          System.IO

import                          Text.JSON

-- | Combine a config flag with a command-line flag, checking for conflicts.
combOverride :: Bool -> Bool -> MaybeOverride
combOverride :: Bool -> Bool -> MaybeOverride
combOverride Bool
False Bool
False = MaybeOverride
NoOverride
combOverride Bool
True  Bool
False = MaybeOverride
OverrideTrue
combOverride Bool
False Bool
True  = MaybeOverride
OverrideFalse
combOverride Bool
True  Bool
True  = MaybeOverride
Conflict

-- | Convert a 'MaybeOverride' to a function on 'Bool'.
overrideFunc :: MaybeOverride -> (Bool -> Bool)
overrideFunc :: MaybeOverride -> Bool -> Bool
overrideFunc MaybeOverride
NoOverride    = Bool -> Bool
forall a. a -> a
id
overrideFunc MaybeOverride
OverrideTrue  = Bool -> Bool -> Bool
forall a b. a -> b -> a
const Bool
True
overrideFunc MaybeOverride
OverrideFalse = Bool -> Bool -> Bool
forall a b. a -> b -> a
const Bool
False
overrideFunc MaybeOverride
Conflict      = [Char] -> Bool -> Bool
forall a. HasCallStack => [Char] -> a
error [Char]
"override conflict!"

-- | Apply the override options to a 'Config'.
overrideConfig :: OverrideOptions -> Config -> Config
overrideConfig :: OverrideOptions -> Config -> Config
overrideConfig OverrideOptions
opts =
  ASetter Config Config Bool Bool
-> (Bool -> Bool) -> Config -> Config
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Config Config Bool Bool
Lens' Config Bool
failOnError            (MaybeOverride -> Bool -> Bool
overrideFunc (MaybeOverride -> Bool -> Bool) -> MaybeOverride -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$         OverrideOptions -> MaybeOverride
overrideFailOnError OverrideOptions
opts)
  (Config -> Config) -> (Config -> Config) -> Config -> Config
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ASetter Config Config Bool Bool
-> (Bool -> Bool) -> Config -> Config
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Config Config Bool Bool
Lens' Config Bool
displayCreationTime  (MaybeOverride -> Bool -> Bool
overrideFunc (MaybeOverride -> Bool -> Bool) -> MaybeOverride -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ OverrideOptions -> MaybeOverride
overrideDisplayCreationTime OverrideOptions
opts)
  (Config -> Config) -> (Config -> Config) -> Config -> Config
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ASetter Config Config Bool Bool
-> (Bool -> Bool) -> Config -> Config
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Config Config Bool Bool
Lens' Config Bool
enableClearing       (MaybeOverride -> Bool -> Bool
overrideFunc (MaybeOverride -> Bool -> Bool) -> MaybeOverride -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$      OverrideOptions -> MaybeOverride
overrideEnableClearing OverrideOptions
opts)
  (Config -> Config) -> (Config -> Config) -> Config -> Config
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ASetter Config Config Bool Bool
-> (Bool -> Bool) -> Config -> Config
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Config Config Bool Bool
Lens' Config Bool
enableReset          (MaybeOverride -> Bool -> Bool
overrideFunc (MaybeOverride -> Bool -> Bool) -> MaybeOverride -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$         OverrideOptions -> MaybeOverride
overrideEnableReset OverrideOptions
opts)
  (Config -> Config) -> (Config -> Config) -> Config -> Config
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ASetter Config Config Bool Bool
-> (Bool -> Bool) -> Config -> Config
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Config Config Bool Bool
Lens' Config Bool
backupBeforeClear    (MaybeOverride -> Bool -> Bool
overrideFunc (MaybeOverride -> Bool -> Bool) -> MaybeOverride -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$   OverrideOptions -> MaybeOverride
overrideBackupBeforeClear OverrideOptions
opts)

-- | Apply the override options to an 'Env'.
overrideEnv :: OverrideOptions -> Env -> Env
overrideEnv :: OverrideOptions -> Env -> Env
overrideEnv = ASetter Env Env Config Config -> (Config -> Config) -> Env -> Env
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Env Env Config Config
Lens' Env Config
config ((Config -> Config) -> Env -> Env)
-> (OverrideOptions -> Config -> Config)
-> OverrideOptions
-> Env
-> Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OverrideOptions -> Config -> Config
overrideConfig

-- | Check that there are no conflicting overrides.
verifyOverrides :: OverrideOptions -> Maybe T.Text
verifyOverrides :: OverrideOptions -> Maybe Text
verifyOverrides (OverrideOptions MaybeOverride
o1 MaybeOverride
o2 MaybeOverride
o3 MaybeOverride
o4 MaybeOverride
o5) = MaybeOverride -> Maybe Text
forall a. IsString a => MaybeOverride -> Maybe a
verify MaybeOverride
o1
                                               Maybe Text -> Maybe Text -> Maybe Text
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> MaybeOverride -> Maybe Text
forall a. IsString a => MaybeOverride -> Maybe a
verify MaybeOverride
o2
                                               Maybe Text -> Maybe Text -> Maybe Text
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> MaybeOverride -> Maybe Text
forall a. IsString a => MaybeOverride -> Maybe a
verify MaybeOverride
o3
                                               Maybe Text -> Maybe Text -> Maybe Text
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> MaybeOverride -> Maybe Text
forall a. IsString a => MaybeOverride -> Maybe a
verify MaybeOverride
o4
                                               Maybe Text -> Maybe Text -> Maybe Text
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> MaybeOverride -> Maybe Text
forall a. IsString a => MaybeOverride -> Maybe a
verify MaybeOverride
o5
  where verify :: MaybeOverride -> Maybe a
verify MaybeOverride
Conflict = a -> Maybe a
forall a. a -> Maybe a
Just a
"conflicting flags"
        verify MaybeOverride
_        = Maybe a
forall a. Maybe a
Nothing

-- | The default behaviour is to override nothing.
defaultOverrideOptions :: OverrideOptions
defaultOverrideOptions :: OverrideOptions
defaultOverrideOptions = MaybeOverride
-> MaybeOverride
-> MaybeOverride
-> MaybeOverride
-> MaybeOverride
-> OverrideOptions
OverrideOptions MaybeOverride
NoOverride
                                         MaybeOverride
NoOverride
                                         MaybeOverride
NoOverride
                                         MaybeOverride
NoOverride
                                         MaybeOverride
NoOverride

-- | Default global options. In general this should do nothing.
defaultGlobalOptions :: GlobalOptions
defaultGlobalOptions :: GlobalOptions
defaultGlobalOptions = Maybe [Char] -> Maybe [Char] -> OverrideOptions -> GlobalOptions
GlobalOptions Maybe [Char]
forall a. Maybe a
Nothing Maybe [Char]
forall a. Maybe a
Nothing OverrideOptions
defaultOverrideOptions

-- | Helper function whenever we need to modify the saved bookmarks.
--
-- @modifyBookmarks f@ retrieves the current bookmarks, applies @f@,
-- and saves them back to file.
modifyBookmarks :: ([Bookmark] -> [Bookmark]) -> HoyoMonad ()
modifyBookmarks :: ([Bookmark] -> [Bookmark]) -> HoyoMonad ()
modifyBookmarks [Bookmark] -> [Bookmark]
f = ([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ()
modifyBookmarksM ([Bookmark] -> HoyoMonad [Bookmark]
forall (m :: * -> *) a. Monad m => a -> m a
return ([Bookmark] -> HoyoMonad [Bookmark])
-> ([Bookmark] -> [Bookmark]) -> [Bookmark] -> HoyoMonad [Bookmark]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Bookmark] -> [Bookmark]
f)

-- | Helper function twhenever we need to modify the saved bookmarks,
-- and need access to the hoyo environment.
--
-- @modifyBookmarks f@ retrieves the current bookmarks, applies @f@
-- in the hoyo environment, and saves them back to file.
modifyBookmarksM :: ([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ()
modifyBookmarksM :: ([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ()
modifyBookmarksM [Bookmark] -> HoyoMonad [Bookmark]
f = do
  Env (Bookmarks [Bookmark]
bms) [Char]
bFp Config
_ [Char]
_ <- HoyoMonad Env
forall r (m :: * -> *). MonadReader r m => m r
ask
  Bookmarks
newBookmarks <- [Bookmark] -> Bookmarks
Bookmarks ([Bookmark] -> Bookmarks)
-> HoyoMonad [Bookmark] -> HoyoMonad Bookmarks
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Bookmark] -> HoyoMonad [Bookmark]
f [Bookmark]
bms
  [Char] -> Bookmarks -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => [Char] -> Bookmarks -> m ()
encodeBookmarksFile [Char]
bFp Bookmarks
newBookmarks

-- | Normalise a filepath and make sure it's a valid directory.
normaliseAndVerifyDirectory :: FilePath -> HoyoMonad FilePath
normaliseAndVerifyDirectory :: [Char] -> HoyoMonad [Char]
normaliseAndVerifyDirectory [Char]
d = do
  [Char]
dir <- IO [Char] -> HoyoMonad [Char]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [Char] -> HoyoMonad [Char]) -> IO [Char] -> HoyoMonad [Char]
forall a b. (a -> b) -> a -> b
$ [Char] -> IO [Char]
canonicalizePath [Char]
d
  HoyoException -> HoyoMonad Bool -> HoyoMonad Bool
assertVerbose (FileSystemException -> HoyoException
FileSystemException (FileSystemException -> HoyoException)
-> FileSystemException -> HoyoException
forall a b. (a -> b) -> a -> b
$ [Char] -> FileSystemException
NoDirException [Char]
dir) (HoyoMonad Bool -> HoyoMonad Bool)
-> HoyoMonad Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ IO Bool -> HoyoMonad Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> HoyoMonad Bool) -> IO Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ [Char] -> IO Bool
doesDirectoryExist [Char]
dir
  [Char] -> HoyoMonad [Char]
forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
dir

-- | Take a name and make sure it's valid.
verifyName :: T.Text -> HoyoMonad ()
verifyName :: Text -> HoyoMonad ()
verifyName Text
name = do
  let nameStr :: [Char]
nameStr = Text -> [Char]
T.unpack Text
name
  HoyoException -> HoyoMonad Bool -> HoyoMonad ()
assert (CommandException -> HoyoException
CommandException (CommandException -> HoyoException)
-> CommandException -> HoyoException
forall a b. (a -> b) -> a -> b
$ [Text] -> CommandException
InvalidArgumentException [Text
"bookmark name can't be empty"])
    (HoyoMonad Bool -> HoyoMonad ()) -> HoyoMonad Bool -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ Bool -> HoyoMonad Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> HoyoMonad Bool) -> Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Text -> Bool
T.null Text
name
  HoyoException -> HoyoMonad Bool -> HoyoMonad ()
assert (CommandException -> HoyoException
CommandException (CommandException -> HoyoException)
-> CommandException -> HoyoException
forall a b. (a -> b) -> a -> b
$ [Text] -> CommandException
InvalidArgumentException [Text
"bookmark name can't be a number"])
    (HoyoMonad Bool -> HoyoMonad ()) -> HoyoMonad Bool -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ Bool -> HoyoMonad Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> HoyoMonad Bool) -> Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> [Char] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit [Char]
nameStr

-- | Given the existing bookmarks and a potential bookmark name,
-- test if the new bookmark will have a unique name.
testNameUnique :: [Bookmark] -> T.Text -> HoyoMonad Bool
testNameUnique :: [Bookmark] -> Text -> HoyoMonad Bool
testNameUnique [Bookmark]
bms Text
name =
  HoyoException -> HoyoMonad Bool -> HoyoMonad Bool
assertVerbose (CommandException -> HoyoException
CommandException (CommandException -> HoyoException)
-> CommandException -> HoyoException
forall a b. (a -> b) -> a -> b
$ [Text] -> CommandException
InvalidArgumentException [Text
"bookmark name already used"])
    (HoyoMonad Bool -> HoyoMonad Bool)
-> HoyoMonad Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Bool -> HoyoMonad Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> HoyoMonad Bool) -> Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ (Bookmark -> Bool) -> [Bookmark] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all ((Maybe Text -> Maybe Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text -> Maybe Text
forall a. a -> Maybe a
Just Text
name) (Maybe Text -> Bool)
-> (Bookmark -> Maybe Text) -> Bookmark -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Getting (Maybe Text) Bookmark (Maybe Text)
-> Bookmark -> Maybe Text
forall a s. Getting a s a -> s -> a
view Getting (Maybe Text) Bookmark (Maybe Text)
Lens' Bookmark (Maybe Text)
bookmarkName) [Bookmark]
bms

-- | Run the "add" command: add a new bookmark.
runAdd :: AddOptions -> HoyoMonad ()
runAdd :: AddOptions -> HoyoMonad ()
runAdd AddOptions
opts = do
  [Char]
dir <- [Char] -> HoyoMonad [Char]
normaliseAndVerifyDirectory ([Char] -> HoyoMonad [Char]) -> [Char] -> HoyoMonad [Char]
forall a b. (a -> b) -> a -> b
$ AddOptions -> [Char]
addDirectory AddOptions
opts
  let name :: Maybe Text
name = AddOptions -> Maybe Text
addName AddOptions
opts
  ([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ()
modifyBookmarksM (([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ())
-> ([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ \[Bookmark]
bms -> do
    Bool
uniqName <- case Maybe Text
name of Maybe Text
Nothing -> Bool -> HoyoMonad Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
                             Just Text
n  -> Text -> HoyoMonad ()
verifyName Text
n HoyoMonad () -> HoyoMonad Bool -> HoyoMonad Bool
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [Bookmark] -> Text -> HoyoMonad Bool
testNameUnique [Bookmark]
bms Text
n
    if Bool
uniqName
    then do
      let maxIndex :: Int
maxIndex = Int -> [Int] -> Int
forall a. Ord a => a -> [a] -> a
maximumDefault Int
0 ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (Bookmark -> Int) -> [Bookmark] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Getting Int Bookmark Int -> Bookmark -> Int
forall a s. Getting a s a -> s -> a
view Getting Int Bookmark Int
Lens' Bookmark Int
bookmarkIndex) [Bookmark]
bms
      ZonedTime
zTime <- IO ZonedTime -> HoyoMonad ZonedTime
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO ZonedTime
getZonedTime
      let newBookMark :: Bookmark
newBookMark = [Char] -> Int -> ZonedTime -> Maybe Text -> Bookmark
Bookmark [Char]
dir (Int
maxIndex Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) ZonedTime
zTime Maybe Text
name
      [Bookmark] -> HoyoMonad [Bookmark]
forall (m :: * -> *) a. Monad m => a -> m a
return (Bookmark
newBookMark Bookmark -> [Bookmark] -> [Bookmark]
forall a. a -> [a] -> [a]
: [Bookmark]
bms)
    else [Bookmark] -> HoyoMonad [Bookmark]
forall (m :: * -> *) a. Monad m => a -> m a
return [Bookmark]
bms

-- | Run the "move" command: search for a bookmark and @cd@ to it.
runMove :: MoveOptions -> HoyoMonad ()
runMove :: MoveOptions -> HoyoMonad ()
runMove MoveOptions
opts = do
  Bookmarks
bms <- SimpleGetter Env Bookmarks -> HoyoMonad Bookmarks
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env Bookmarks
Lens' Env Bookmarks
bookmarks
  let search :: BookmarkSearchTerm
search = MoveOptions -> BookmarkSearchTerm
moveSearch MoveOptions
opts
  case ([Bookmark], [Bookmark]) -> [Bookmark]
forall a b. (a, b) -> a
fst (([Bookmark], [Bookmark]) -> [Bookmark])
-> ([Bookmark], [Bookmark]) -> [Bookmark]
forall a b. (a -> b) -> a -> b
$ BookmarkSearchTerm -> Bookmarks -> ([Bookmark], [Bookmark])
searchBookmarks BookmarkSearchTerm
search Bookmarks
bms of
    []    -> HoyoException -> HoyoMonad ()
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (HoyoException -> HoyoMonad ()) -> HoyoException -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ CommandException -> HoyoException
CommandException (CommandException -> HoyoException)
-> CommandException -> HoyoException
forall a b. (a -> b) -> a -> b
$ SearchException -> CommandException
SearchException (SearchException -> CommandException)
-> SearchException -> CommandException
forall a b. (a -> b) -> a -> b
$ BookmarkSearchTerm -> SearchException
NothingFound BookmarkSearchTerm
search
    [Bookmark
bm]  -> do Text -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStdout (Text
"cd " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Char] -> Text
T.pack (Getting [Char] Bookmark [Char] -> Bookmark -> [Char]
forall a s. Getting a s a -> s -> a
view Getting [Char] Bookmark [Char]
Lens' Bookmark [Char]
bookmarkDirectory Bookmark
bm))
                IO () -> HoyoMonad ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> HoyoMonad ()) -> IO () -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ ExitCode -> IO ()
forall a. ExitCode -> IO a
exitWith (Int -> ExitCode
ExitFailure Int
3)
    [Bookmark]
ms    -> do Bool
displayTime <- SimpleGetter Env Bool -> HoyoMonad Bool
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Bool -> Const r Bool) -> Config -> Const r Config)
-> (Bool -> Const r Bool)
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool -> Const r Bool) -> Config -> Const r Config
Lens' Config Bool
displayCreationTime)
                let strs :: [Text]
strs = Bool -> [Bookmark] -> [Text]
formatBookmarks Bool
displayTime ([Bookmark] -> [Text]) -> [Bookmark] -> [Text]
forall a b. (a -> b) -> a -> b
$ (Bookmark -> Int) -> [Bookmark] -> [Bookmark]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (Getting Int Bookmark Int -> Bookmark -> Int
forall a s. Getting a s a -> s -> a
view Getting Int Bookmark Int
Lens' Bookmark Int
bookmarkIndex) [Bookmark]
ms
                HoyoException -> HoyoMonad ()
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (HoyoException -> HoyoMonad ()) -> HoyoException -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ CommandException -> HoyoException
CommandException (CommandException -> HoyoException)
-> CommandException -> HoyoException
forall a b. (a -> b) -> a -> b
$ SearchException -> CommandException
SearchException (SearchException -> CommandException)
-> SearchException -> CommandException
forall a b. (a -> b) -> a -> b
$ BookmarkSearchTerm -> [Text] -> SearchException
TooManyResults BookmarkSearchTerm
search [Text]
strs

-- | Run the "list" command: list all the saved bookmarks.
runList :: ListOptions -> HoyoMonad ()
runList :: ListOptions -> HoyoMonad ()
runList ListOptions
opts = do
  let filt :: Bookmark -> Bool
filt = Maybe Text -> Maybe Text -> Bookmark -> Bool
filterBookmarks (ListOptions -> Maybe Text
listFilterName ListOptions
opts) (ListOptions -> Maybe Text
listFilterDirectoryInfix ListOptions
opts)
  Bookmarks
bms' <- SimpleGetter Env Bookmarks -> HoyoMonad Bookmarks
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env Bookmarks
Lens' Env Bookmarks
bookmarks
  let bms :: [Bookmark]
bms = (Bookmark -> Bool) -> [Bookmark] -> [Bookmark]
forall a. (a -> Bool) -> [a] -> [a]
filter Bookmark -> Bool
filt ([Bookmark] -> [Bookmark]) -> [Bookmark] -> [Bookmark]
forall a b. (a -> b) -> a -> b
$ (Bookmark -> Int) -> [Bookmark] -> [Bookmark]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (Getting Int Bookmark Int -> Bookmark -> Int
forall a s. Getting a s a -> s -> a
view Getting Int Bookmark Int
Lens' Bookmark Int
bookmarkIndex) ([Bookmark] -> [Bookmark]) -> [Bookmark] -> [Bookmark]
forall a b. (a -> b) -> a -> b
$ Bookmarks -> [Bookmark]
unBookmarks Bookmarks
bms'
  Bool
displayTime <- SimpleGetter Env Bool -> HoyoMonad Bool
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Bool -> Const r Bool) -> Config -> Const r Config)
-> (Bool -> Const r Bool)
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool -> Const r Bool) -> Config -> Const r Config
Lens' Config Bool
displayCreationTime)
  if ListOptions -> Bool
listJSONOutput ListOptions
opts
  then do
    let jsonObj :: JSValue
jsonObj = Bool -> [Bookmark] -> JSValue
bookmarksToJSON Bool
displayTime [Bookmark]
bms
    Text -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStdout (Text -> HoyoMonad ()) -> Text -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ [Char] -> Text
T.pack ([Char] -> Text) -> [Char] -> Text
forall a b. (a -> b) -> a -> b
$ JSValue -> [Char]
forall a. JSON a => a -> [Char]
encode JSValue
jsonObj
  else [Text] -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => [Text] -> m ()
pageLines (Bool -> [Bookmark] -> [Text]
formatBookmarks Bool
displayTime [Bookmark]
bms)

-- | Help text displayed when the user tries to run "hoyo clear"
-- when "enable_clear" is set to false.
clearDisabledErr :: HoyoException
clearDisabledErr :: HoyoException
clearDisabledErr = [Text] -> HoyoException
ConfigException [
  Text
"The 'clear' command is disabled by default."
  , Text
"To enable, set enable_clear = true in the config or pass the --enable-clear flag."
  ]

-- | Help text displayed when the user tries to run "hoyo config reset"
-- when "enable_reset" is set to false.
resetDisabledErr :: HoyoException
resetDisabledErr :: HoyoException
resetDisabledErr = [Text] -> HoyoException
ConfigException [
  Text
"The 'config reset' command is disabled by default."
  , Text
"To enable, set enable_reset = true in the config or pass the --enable-reset flag."
  ]

-- | Prompt the user for confirmation with the given string.
getConfirmation :: String -> IO Bool
getConfirmation :: [Char] -> IO Bool
getConfirmation [Char]
s = IO () -> IO () -> IO Bool -> IO Bool
forall a b c. IO a -> IO b -> IO c -> IO c
bracket_ IO ()
makeRed IO ()
resetColour IO Bool
getConfirmation'
  where
    makeRed :: IO ()
makeRed = Handle -> [SGR] -> IO ()
hSetSGR Handle
stdout [ConsoleLayer -> ColorIntensity -> Color -> SGR
SetColor ConsoleLayer
Foreground ColorIntensity
Vivid Color
Red]
    resetColour :: IO ()
resetColour = Handle -> [SGR] -> IO ()
hSetSGR Handle
stdout [] IO () -> IO () -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [Char] -> IO ()
putStrLn [Char]
"\n"
    getConfirmation' :: IO Bool
getConfirmation' = do
      [Char] -> IO ()
putStr ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char]
s [Char] -> [Char] -> [Char]
forall a. Semigroup a => a -> a -> a
<> [Char]
" (y/n): "
      Handle -> IO ()
hFlush Handle
stdout
      [Char]
inp <- IO [Char]
getLine
      case [Char]
inp of
        [Char]
"y" -> Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
        [Char]
"Y" -> Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
        [Char]
"n" -> Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
        [Char]
"N" -> Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
        [Char]
_   -> IO Bool
getConfirmation'

-- | Run the "clear" command: delete all the saved bookmarks.
runClear :: ClearOptions -> HoyoMonad ()
runClear :: ClearOptions -> HoyoMonad ()
runClear ClearOptions
_ = do
  HoyoException -> HoyoMonad Bool -> HoyoMonad ()
assert HoyoException
clearDisabledErr (SimpleGetter Env Bool -> HoyoMonad Bool
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Bool -> Const r Bool) -> Config -> Const r Config)
-> (Bool -> Const r Bool)
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool -> Const r Bool) -> Config -> Const r Config
Lens' Config Bool
enableClearing))

  [Char]
path <- SimpleGetter Env [Char] -> HoyoMonad [Char]
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env [Char]
Lens' Env [Char]
bookmarksPath
  Bool
backup <- SimpleGetter Env Bool -> HoyoMonad Bool
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Bool -> Const r Bool) -> Config -> Const r Config)
-> (Bool -> Const r Bool)
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool -> Const r Bool) -> Config -> Const r Config
Lens' Config Bool
backupBeforeClear)
  Bool -> HoyoMonad () -> HoyoMonad ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
backup (HoyoMonad () -> HoyoMonad ()) -> HoyoMonad () -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> HoyoMonad ()
forall (m :: * -> *).
(MonadIO m, MonadError HoyoException m) =>
[Char] -> [Char] -> m ()
backupFile [Char]
path [Char]
"bkp"

  Bool
isTTY <- IO Bool -> HoyoMonad Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> HoyoMonad Bool) -> IO Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Handle -> IO Bool
hIsTerminalDevice Handle
stdin

  if Bool
backup Bool -> Bool -> Bool
|| Bool -> Bool
not Bool
isTTY
  then HoyoMonad ()
runClear'
  else do
    Bool
yes <- IO Bool -> HoyoMonad Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> HoyoMonad Bool) -> IO Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ [Char] -> IO Bool
getConfirmation [Char]
"Are you sure you want to delete all your saved backups?"
    Bool -> HoyoMonad () -> HoyoMonad ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
yes HoyoMonad ()
runClear'

  where
    runClear' :: HoyoMonad ()
runClear' = do
      ([Bookmark] -> [Bookmark]) -> HoyoMonad ()
modifyBookmarks (([Bookmark] -> [Bookmark]) -> HoyoMonad ())
-> ([Bookmark] -> [Bookmark]) -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ [Bookmark] -> [Bookmark] -> [Bookmark]
forall a b. a -> b -> a
const []
      Bookmarks
bms <- SimpleGetter Env [DefaultBookmark] -> HoyoMonad [DefaultBookmark]
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> (([DefaultBookmark] -> Const r [DefaultBookmark])
    -> Config -> Const r Config)
-> ([DefaultBookmark] -> Const r [DefaultBookmark])
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([DefaultBookmark] -> Const r [DefaultBookmark])
-> Config -> Const r Config
Lens' Config [DefaultBookmark]
defaultBookmarks) HoyoMonad [DefaultBookmark]
-> ([DefaultBookmark] -> HoyoMonad Bookmarks)
-> HoyoMonad Bookmarks
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [DefaultBookmark] -> HoyoMonad Bookmarks
forall (m :: * -> *). MonadIO m => [DefaultBookmark] -> m Bookmarks
bookmarksFromDefault
      [Char]
fp <- SimpleGetter Env [Char] -> HoyoMonad [Char]
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env [Char]
Lens' Env [Char]
bookmarksPath
      [Char] -> Bookmarks -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => [Char] -> Bookmarks -> m ()
encodeBookmarksFile [Char]
fp Bookmarks
bms

-- | Run the "delete" command: search for a bookmark and delete it.
runDelete :: DeleteOptions -> HoyoMonad ()
runDelete :: DeleteOptions -> HoyoMonad ()
runDelete DeleteOptions
opts = do
  let search :: BookmarkSearchTerm
search = DeleteOptions -> BookmarkSearchTerm
deleteSearch DeleteOptions
opts
  ([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ()
modifyBookmarksM (([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ())
-> ([Bookmark] -> HoyoMonad [Bookmark]) -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ \[Bookmark]
bms -> do
    let ([Bookmark]
searchResults, [Bookmark]
afterDelete) = BookmarkSearchTerm -> Bookmarks -> ([Bookmark], [Bookmark])
searchBookmarks BookmarkSearchTerm
search ([Bookmark] -> Bookmarks
Bookmarks [Bookmark]
bms)
    HoyoException -> HoyoMonad Bool -> HoyoMonad Bool
assertVerbose (CommandException -> HoyoException
CommandException (CommandException -> HoyoException)
-> CommandException -> HoyoException
forall a b. (a -> b) -> a -> b
$ SearchException -> CommandException
SearchException (SearchException -> CommandException)
-> SearchException -> CommandException
forall a b. (a -> b) -> a -> b
$ BookmarkSearchTerm -> SearchException
NothingFound BookmarkSearchTerm
search)
      (HoyoMonad Bool -> HoyoMonad Bool)
-> HoyoMonad Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Bool -> HoyoMonad Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> HoyoMonad Bool) -> Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [Bookmark] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Bookmark]
searchResults
    Bool
displayTime <- SimpleGetter Env Bool -> HoyoMonad Bool
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Bool -> Const r Bool) -> Config -> Const r Config)
-> (Bool -> Const r Bool)
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool -> Const r Bool) -> Config -> Const r Config
Lens' Config Bool
displayCreationTime)
    let strs :: [Text]
strs = Bool -> [Bookmark] -> [Text]
formatBookmarks Bool
displayTime ([Bookmark] -> [Text]) -> [Bookmark] -> [Text]
forall a b. (a -> b) -> a -> b
$ (Bookmark -> Int) -> [Bookmark] -> [Bookmark]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (Getting Int Bookmark Int -> Bookmark -> Int
forall a s. Getting a s a -> s -> a
view Getting Int Bookmark Int
Lens' Bookmark Int
bookmarkIndex) [Bookmark]
searchResults
    HoyoException -> HoyoMonad Bool -> HoyoMonad Bool
assertVerbose (CommandException -> HoyoException
CommandException (CommandException -> HoyoException)
-> CommandException -> HoyoException
forall a b. (a -> b) -> a -> b
$ SearchException -> CommandException
SearchException (SearchException -> CommandException)
-> SearchException -> CommandException
forall a b. (a -> b) -> a -> b
$ BookmarkSearchTerm -> [Text] -> SearchException
TooManyResults BookmarkSearchTerm
search [Text]
strs)
      (HoyoMonad Bool -> HoyoMonad Bool)
-> HoyoMonad Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Bool -> HoyoMonad Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> HoyoMonad Bool) -> Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ [Bookmark] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Bookmark]
searchResults Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1
    [Bookmark] -> HoyoMonad [Bookmark]
forall (m :: * -> *) a. Monad m => a -> m a
return [Bookmark]
afterDelete

-- | Run the "refresh" command: re-index bookmarks.
runRefresh :: RefreshOptions -> HoyoMonad ()
runRefresh :: RefreshOptions -> HoyoMonad ()
runRefresh RefreshOptions
_ = ([Bookmark] -> [Bookmark]) -> HoyoMonad ()
modifyBookmarks (([Bookmark] -> [Bookmark]) -> HoyoMonad ())
-> ([Bookmark] -> [Bookmark]) -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$
  (Int -> Bookmark -> Bookmark) -> [Int] -> [Bookmark] -> [Bookmark]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (ASetter Bookmark Bookmark Int Int -> Int -> Bookmark -> Bookmark
forall s t a b. ASetter s t a b -> b -> s -> t
set ASetter Bookmark Bookmark Int Int
Lens' Bookmark Int
bookmarkIndex) [Int
1..]                           -- re-index from 1
    ([Bookmark] -> [Bookmark])
-> ([Bookmark] -> [Bookmark]) -> [Bookmark] -> [Bookmark]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bookmark -> Bookmark -> Bool) -> [Bookmark] -> [Bookmark]
forall a. (a -> a -> Bool) -> [a] -> [a]
nubBy ([Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
(==) ([Char] -> [Char] -> Bool)
-> (Bookmark -> [Char]) -> Bookmark -> Bookmark -> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` Getting [Char] Bookmark [Char] -> Bookmark -> [Char]
forall a s. Getting a s a -> s -> a
view Getting [Char] Bookmark [Char]
Lens' Bookmark [Char]
bookmarkDirectory)                -- remove duplicate directories
    ([Bookmark] -> [Bookmark])
-> ([Bookmark] -> [Bookmark]) -> [Bookmark] -> [Bookmark]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bookmark -> UTCTime) -> [Bookmark] -> [Bookmark]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (ZonedTime -> UTCTime
zonedTimeToUTC (ZonedTime -> UTCTime)
-> (Bookmark -> ZonedTime) -> Bookmark -> UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Getting ZonedTime Bookmark ZonedTime -> Bookmark -> ZonedTime
forall a s. Getting a s a -> s -> a
view Getting ZonedTime Bookmark ZonedTime
Lens' Bookmark ZonedTime
bookmarkCreationTime)     -- sort by creation time

-- | Run the "config print" command: print the current config.
runConfigPrint :: ConfigPrintOptions -> HoyoMonad ()
runConfigPrint :: ConfigPrintOptions -> HoyoMonad ()
runConfigPrint ConfigPrintOptions
opts = do
  Config
s <- SimpleGetter Env Config -> HoyoMonad Config
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env Config
Lens' Env Config
config
  let keyVals :: [(Text, AnyConfigValue)]
keyVals = Config -> [(Text, AnyConfigValue)]
getKeyVals Config
s
  let keyWidth :: Int
keyWidth = [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ ((Text, AnyConfigValue) -> Int)
-> [(Text, AnyConfigValue)] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Int
T.length (Text -> Int)
-> ((Text, AnyConfigValue) -> Text)
-> (Text, AnyConfigValue)
-> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text, AnyConfigValue) -> Text
forall a b. (a, b) -> a
fst) [(Text, AnyConfigValue)]
keyVals
  if ConfigPrintOptions -> Bool
configPrintJSONOuput ConfigPrintOptions
opts
  then do
    let jsonObj :: JSValue
jsonObj = JSObject JSValue -> JSValue
JSObject (JSObject JSValue -> JSValue) -> JSObject JSValue -> JSValue
forall a b. (a -> b) -> a -> b
$ [([Char], JSValue)] -> JSObject JSValue
forall a. [([Char], a)] -> JSObject a
toJSObject ([([Char], JSValue)] -> JSObject JSValue)
-> [([Char], JSValue)] -> JSObject JSValue
forall a b. (a -> b) -> a -> b
$ ((Text, AnyConfigValue) -> ([Char], JSValue))
-> [(Text, AnyConfigValue)] -> [([Char], JSValue)]
forall a b. (a -> b) -> [a] -> [b]
map ((Text -> [Char])
-> (AnyConfigValue -> JSValue)
-> (Text, AnyConfigValue)
-> ([Char], JSValue)
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap Text -> [Char]
T.unpack AnyConfigValue -> JSValue
anyCfgValToJson) [(Text, AnyConfigValue)]
keyVals
    Text -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStdout (Text -> HoyoMonad ()) -> Text -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ [Char] -> Text
T.pack ([Char] -> Text) -> [Char] -> Text
forall a b. (a -> b) -> a -> b
$ JSValue -> [Char]
forall a. JSON a => a -> [Char]
encode JSValue
jsonObj
  else do
    let align :: Text -> Text
align = Int -> Char -> Text -> Text
T.justifyLeft Int
keyWidth Char
' '
    let ls :: [Text]
ls = ((Text, AnyConfigValue) -> Text)
-> [(Text, AnyConfigValue)] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (\(Text
k, AnyConfigValue
v) -> Text -> Text
align Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" = " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> AnyConfigValue -> Text
formatConfigValue AnyConfigValue
v) [(Text, AnyConfigValue)]
keyVals
    [Text] -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => [Text] -> m ()
pageLines [Text]
ls

-- | Run the "config reset" command: reset the config to 'defaultConfig'.
runConfigReset :: ConfigResetOptions -> HoyoMonad ()
runConfigReset :: ConfigResetOptions -> HoyoMonad ()
runConfigReset ConfigResetOptions
_ = do
  HoyoException -> HoyoMonad Bool -> HoyoMonad ()
assert HoyoException
resetDisabledErr (SimpleGetter Env Bool -> HoyoMonad Bool
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Bool -> Const r Bool) -> Config -> Const r Config)
-> (Bool -> Const r Bool)
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool -> Const r Bool) -> Config -> Const r Config
Lens' Config Bool
enableReset))

  [Char]
path <- SimpleGetter Env [Char] -> HoyoMonad [Char]
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env [Char]
Lens' Env [Char]
configPath

  Bool
backup <- SimpleGetter Env Bool -> HoyoMonad Bool
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Bool -> Const r Bool) -> Config -> Const r Config)
-> (Bool -> Const r Bool)
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bool -> Const r Bool) -> Config -> Const r Config
Lens' Config Bool
backupBeforeClear)
  Bool -> HoyoMonad () -> HoyoMonad ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
backup (HoyoMonad () -> HoyoMonad ()) -> HoyoMonad () -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char] -> HoyoMonad ()
forall (m :: * -> *).
(MonadIO m, MonadError HoyoException m) =>
[Char] -> [Char] -> m ()
backupFile [Char]
path [Char]
"bkp"

  Bool
isTTY <- IO Bool -> HoyoMonad Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> HoyoMonad Bool) -> IO Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ Handle -> IO Bool
hIsTerminalDevice Handle
stdin

  if Bool
backup Bool -> Bool -> Bool
|| Bool -> Bool
not Bool
isTTY
  then HoyoMonad (Either HoyoException ()) -> HoyoMonad ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (HoyoMonad (Either HoyoException ()) -> HoyoMonad ())
-> HoyoMonad (Either HoyoException ()) -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ [Char] -> Config -> HoyoMonad (Either HoyoException ())
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
[Char] -> Config -> m (Either HoyoException ())
encodeConfigFile [Char]
path Config
defaultConfig
  else do
    Bool
yes <- IO Bool -> HoyoMonad Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> HoyoMonad Bool) -> IO Bool -> HoyoMonad Bool
forall a b. (a -> b) -> a -> b
$ [Char] -> IO Bool
getConfirmation [Char]
"Are you sure you want to clear your hoyo configuration?"
    Bool -> HoyoMonad () -> HoyoMonad ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
yes (HoyoMonad () -> HoyoMonad ()) -> HoyoMonad () -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ HoyoMonad (Either HoyoException ()) -> HoyoMonad ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (HoyoMonad (Either HoyoException ()) -> HoyoMonad ())
-> HoyoMonad (Either HoyoException ()) -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ [Char] -> Config -> HoyoMonad (Either HoyoException ())
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
[Char] -> Config -> m (Either HoyoException ())
encodeConfigFile [Char]
path Config
defaultConfig

-- | Run the "config set" command: try to set a key-value pair in the config.
runConfigSet :: ConfigSetOptions -> HoyoMonad ()
runConfigSet :: ConfigSetOptions -> HoyoMonad ()
runConfigSet ConfigSetOptions
opts = do
  let key :: Text
key = ConfigSetOptions -> Text
setKey ConfigSetOptions
opts
  let val :: Text
val = ConfigSetOptions -> Text
setValue ConfigSetOptions
opts
  [Char]
cfgPath <- SimpleGetter Env [Char] -> HoyoMonad [Char]
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env [Char]
Lens' Env [Char]
configPath
  SimpleGetter Env Config -> HoyoMonad Config
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env Config
Lens' Env Config
config
    HoyoMonad Config
-> (Config -> HoyoMonad Config) -> HoyoMonad Config
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> Text -> Config -> HoyoMonad Config
forall (m :: * -> *).
MonadError HoyoException m =>
Text -> Text -> Config -> m Config
setConfig Text
key Text
val
    HoyoMonad Config -> (Config -> HoyoMonad ()) -> HoyoMonad ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HoyoMonad (Either HoyoException ()) -> HoyoMonad ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (HoyoMonad (Either HoyoException ()) -> HoyoMonad ())
-> (Config -> HoyoMonad (Either HoyoException ()))
-> Config
-> HoyoMonad ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Config -> HoyoMonad (Either HoyoException ())
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
[Char] -> Config -> m (Either HoyoException ())
encodeConfigFile [Char]
cfgPath

-- | Run the "config add-default" command: try to set a key-value pair in the config.
runAddDefault :: ConfigAddDefaultOptions -> HoyoMonad ()
runAddDefault :: ConfigAddDefaultOptions -> HoyoMonad ()
runAddDefault ConfigAddDefaultOptions
opts = do
  [Char]
dir <- [Char] -> HoyoMonad [Char]
normaliseAndVerifyDirectory ([Char] -> HoyoMonad [Char]) -> [Char] -> HoyoMonad [Char]
forall a b. (a -> b) -> a -> b
$ ConfigAddDefaultOptions -> [Char]
addDefaultDir ConfigAddDefaultOptions
opts

  let name :: Maybe Text
name = ConfigAddDefaultOptions -> Maybe Text
addDefaultName ConfigAddDefaultOptions
opts
  let defaultBm :: DefaultBookmark
defaultBm = [Char] -> Maybe Text -> DefaultBookmark
DefaultBookmark [Char]
dir Maybe Text
name
  [Char]
cfgPath <- SimpleGetter Env [Char] -> HoyoMonad [Char]
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env [Char]
Lens' Env [Char]
configPath
  SimpleGetter Env Config -> HoyoMonad Config
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' SimpleGetter Env Config
Lens' Env Config
config
    HoyoMonad Config -> (Config -> HoyoMonad ()) -> HoyoMonad ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HoyoMonad (Either HoyoException ()) -> HoyoMonad ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (HoyoMonad (Either HoyoException ()) -> HoyoMonad ())
-> (Config -> HoyoMonad (Either HoyoException ()))
-> Config
-> HoyoMonad ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Config -> HoyoMonad (Either HoyoException ())
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
[Char] -> Config -> m (Either HoyoException ())
encodeConfigFile [Char]
cfgPath (Config -> HoyoMonad (Either HoyoException ()))
-> (Config -> Config)
-> Config
-> HoyoMonad (Either HoyoException ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ASetter Config Config [DefaultBookmark] [DefaultBookmark]
-> ([DefaultBookmark] -> [DefaultBookmark]) -> Config -> Config
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Config Config [DefaultBookmark] [DefaultBookmark]
Lens' Config [DefaultBookmark]
defaultBookmarks (DefaultBookmark
defaultBm DefaultBookmark -> [DefaultBookmark] -> [DefaultBookmark]
forall a. a -> [a] -> [a]
:)

-- | Run the "config" command: dispatch on the given sub-command.
runConfig :: ConfigCommand -> HoyoMonad ()
runConfig :: ConfigCommand -> HoyoMonad ()
runConfig (Print ConfigPrintOptions
opts)              = ConfigPrintOptions -> HoyoMonad ()
runConfigPrint ConfigPrintOptions
opts
runConfig (Reset ConfigResetOptions
opts)              = ConfigResetOptions -> HoyoMonad ()
runConfigReset ConfigResetOptions
opts
runConfig (Set ConfigSetOptions
opts)                = ConfigSetOptions -> HoyoMonad ()
runConfigSet ConfigSetOptions
opts
runConfig (AddDefaultBookmark ConfigAddDefaultOptions
opts) = ConfigAddDefaultOptions -> HoyoMonad ()
runAddDefault ConfigAddDefaultOptions
opts

-- | Check that the config file is valid.
runCheckConfig :: FilePath -> IO ()
runCheckConfig :: [Char] -> IO ()
runCheckConfig = [Char] -> IO (Either HoyoException Config)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
[Char] -> m (Either HoyoException Config)
decodeConfigFile ([Char] -> IO (Either HoyoException Config))
-> (Either HoyoException Config -> IO ()) -> [Char] -> IO ()
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> \case
  Left HoyoException
err -> Text -> IO ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStderr (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ HoyoException -> Text
formatException HoyoException
err
  Right Config
_  -> Text -> IO ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStdout Text
"Config is good"

-- | Check that the bookmarks file is valid.
runCheckBookmarks :: FilePath -> IO ()
runCheckBookmarks :: [Char] -> IO ()
runCheckBookmarks = [Char] -> IO (Either HoyoException Bookmarks)
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
[Char] -> m (Either HoyoException Bookmarks)
decodeBookmarksFile ([Char] -> IO (Either HoyoException Bookmarks))
-> (Either HoyoException Bookmarks -> IO ()) -> [Char] -> IO ()
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> \case
  Left HoyoException
err -> Text -> IO ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStderr (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ HoyoException -> Text
formatException HoyoException
err
  Right Bookmarks
_  -> Text -> IO ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStdout Text
"Bookmarks file is good"

-- | Run the "config check" command: validate the current
-- config and bookmarks files.
runCheck :: CheckOptions -> FilePath -> FilePath -> IO ()
runCheck :: CheckOptions -> [Char] -> [Char] -> IO ()
runCheck CheckOptions
opts [Char]
bFp [Char]
sFp = do
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (CheckOptions -> Bool
checkConfig CheckOptions
opts) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char] -> IO ()
runCheckConfig [Char]
sFp
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (CheckOptions -> Bool
checkBookmarks CheckOptions
opts) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ [Char] -> IO ()
runCheckBookmarks [Char]
bFp

-- | Run the default command, if it has been specified by the user.
runDefaultCommand :: HoyoMonad ()
runDefaultCommand :: HoyoMonad ()
runDefaultCommand = SimpleGetter Env (Maybe Command) -> HoyoMonad (Maybe Command)
forall a (m :: * -> *) b.
MonadReader a m =>
SimpleGetter a b -> m b
asks' ((Config -> Const r Config) -> Env -> Const r Env
Lens' Env Config
config ((Config -> Const r Config) -> Env -> Const r Env)
-> ((Maybe Command -> Const r (Maybe Command))
    -> Config -> Const r Config)
-> (Maybe Command -> Const r (Maybe Command))
-> Env
-> Const r Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe Command -> Const r (Maybe Command))
-> Config -> Const r Config
Lens' Config (Maybe Command)
defaultCommand) HoyoMonad (Maybe Command)
-> (Maybe Command -> HoyoMonad ()) -> HoyoMonad ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
  Maybe Command
Nothing             -> IO () -> HoyoMonad ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> HoyoMonad ()) -> IO () -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ Maybe [Char] -> IO ()
showHelp Maybe [Char]
forall a. Maybe a
Nothing
  Just Command
DefaultCommand -> HoyoException -> HoyoMonad ()
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (HoyoException -> HoyoMonad ()) -> HoyoException -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ CommandException -> HoyoException
CommandException CommandException
LoopException
  Just Command
otherCommand   -> Command -> HoyoMonad ()
runCommand Command
otherCommand

-- | Run the "help" command: get help on a specific subcommand.
runHelp :: HelpOptions -> HoyoMonad ()
runHelp :: HelpOptions -> HoyoMonad ()
runHelp (HelpOptions Maybe Text
cmd) = IO () -> HoyoMonad ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> HoyoMonad ()) -> IO () -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ Maybe [Char] -> IO ()
showHelp (Text -> [Char]
T.unpack (Text -> [Char]) -> Maybe Text -> Maybe [Char]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Text
cmd)

-- | Run a 'Command' in the hoyo environment.
runCommand :: Command -> HoyoMonad ()
runCommand :: Command -> HoyoMonad ()
runCommand       (Add AddOptions
opts) = AddOptions -> HoyoMonad ()
runAdd AddOptions
opts
runCommand      (Move MoveOptions
opts) = MoveOptions -> HoyoMonad ()
runMove MoveOptions
opts
runCommand      (List ListOptions
opts) = ListOptions -> HoyoMonad ()
runList ListOptions
opts
runCommand     (Clear ClearOptions
opts) = ClearOptions -> HoyoMonad ()
runClear ClearOptions
opts
runCommand    (Delete DeleteOptions
opts) = DeleteOptions -> HoyoMonad ()
runDelete DeleteOptions
opts
runCommand   (Refresh RefreshOptions
opts) = RefreshOptions -> HoyoMonad ()
runRefresh RefreshOptions
opts
runCommand (ConfigCmd ConfigCommand
opts) = ConfigCommand -> HoyoMonad ()
runConfig ConfigCommand
opts
runCommand   Command
DefaultCommand = HoyoMonad ()
runDefaultCommand
runCommand      (Help HelpOptions
opts) = HelpOptions -> HoyoMonad ()
runHelp HelpOptions
opts
runCommand     (Check CheckOptions
opts) = do
  -- printStderr "The 'check' command should be run outside the Hoyo monad."
  Text -> HoyoMonad ()
forall (m :: * -> *). MonadIO m => Text -> m ()
printStderr Text
"It's discouraged to set default_command = \"check\" in your hoyo config file."
  Env Bookmarks
_ [Char]
bFp Config
_ [Char]
sFp <- HoyoMonad Env
forall r (m :: * -> *). MonadReader r m => m r
ask
  IO () -> HoyoMonad ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> HoyoMonad ()) -> IO () -> HoyoMonad ()
forall a b. (a -> b) -> a -> b
$ CheckOptions -> [Char] -> [Char] -> IO ()
runCheck CheckOptions
opts [Char]
bFp [Char]
sFp