------------------------------------------------------------------------------
-- |
-- Module      : BM
-- Description : API
-- Copyright   : Copyright (c) 2021-2022 Travis Cardwell
-- License     : MIT
------------------------------------------------------------------------------

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}

module BM
  ( -- * Constants
    version
    -- * Types
  , Argument
  , Command
  , Error
  , Keyword
  , ParameterName
  , ParameterValue
  , Trace
  , Url
  , Config(..)
  , Bookmark(..)
  , Query(..)
  , Parameter(..)
  , Proc(..)
    -- * API
  , run
  , getCompletion
  ) where

-- https://hackage.haskell.org/package/aeson
import qualified Data.Aeson as A
import Data.Aeson (FromJSON, (.:), (.:?), (.!=))
import qualified Data.Aeson.Types as AT

-- https://hackage.haskell.org/package/base
import Data.List (intercalate, isPrefixOf)
import Data.Maybe (fromMaybe)
import Data.Version (showVersion)
import qualified System.Info

-- https://hackage.haskell.org/package/dlist
import qualified Data.DList as DList
import Data.DList (DList)

-- https://hackage.haskell.org/package/network-uri
import qualified Network.URI as URI

-- https://hackage.haskell.org/package/scientific
import qualified Data.Scientific as Sci

-- https://hackage.haskell.org/package/text
import qualified Data.Text as T

-- https://hackage.haskell.org/package/transformers
import Control.Monad.Trans.Writer (Writer, runWriter, tell)

-- https://hackage.haskell.org/package/vector
import qualified Data.Vector as V
import Data.Vector (Vector)

-- (bm:cabal)
import qualified Paths_bm as Project

------------------------------------------------------------------------------
-- $Constants

-- | bm version string (\"@bm-haskell X.X.X.X@\")
--
-- @since 0.1.0.0
version :: String
version :: String
version = String
"bm-haskell " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Version -> String
showVersion Version
Project.version

------------------------------------------------------------------------------

-- | Default command, depending on the OS
defaultCommand :: Command
defaultCommand :: String
defaultCommand = case String
System.Info.os of
    String
"mingw32" -> String
"start"
    String
"darwin"  -> String
"open"
    String
_other    -> String
"xdg-open"

-- | Default query parameter name
defaultParameter :: ParameterName
defaultParameter :: String
defaultParameter = String
"q"

------------------------------------------------------------------------------
-- $Types
--
-- This implementation makes heavy use of the 'String' type.  Type aliases are
-- provided to make the API easier to read.

-- | CLI argument or process argument
--
-- @since 0.1.0.0
type Argument = String

-- | Process command
--
-- This command is executed with a single URL argument to open a
-- bookmark/query.
--
-- @since 0.1.0.0
type Command = FilePath

-- | Error message
--
-- @since 0.1.0.0
type Error = String

-- | Bookmark keyword
--
-- The configuration file defines a hierarchy of keywords that are matched
-- against CLI arguments to determine which bookmark/query to open.
--
-- @since 0.1.0.0
type Keyword = String

-- | Query parameter name
--
-- @since 0.1.0.0
type ParameterName = String

-- | Query parameter value
--
-- @since 0.1.0.0
type ParameterValue = String

-- | Trace line for debugging
--
-- @since 0.1.0.0
type Trace = String

-- | Bookmark or query action URL
--
-- @since 0.1.0.0
type Url = String

------------------------------------------------------------------------------

-- | Configuration
--
-- YAML attributes:
--
-- * @command@: top-level command (string, default depends on the OS)
-- * @args@: bookmarks (array of 'Bookmark')
--
-- Default commands:
--
-- * Linux: @xdg-open@
-- * Windows: @start@
-- * macOS: @open@
--
-- @since 0.1.0.0
data Config
  = Config
    { Config -> String
configCommand :: !Command
    , Config -> Vector Bookmark
configArgs    :: !(Vector Bookmark)
    }
  deriving Int -> Config -> String -> String
[Config] -> String -> String
Config -> String
(Int -> Config -> String -> String)
-> (Config -> String)
-> ([Config] -> String -> String)
-> Show Config
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [Config] -> String -> String
$cshowList :: [Config] -> String -> String
show :: Config -> String
$cshow :: Config -> String
showsPrec :: Int -> Config -> String -> String
$cshowsPrec :: Int -> Config -> String -> String
Show

instance FromJSON Config where
  parseJSON :: Value -> Parser Config
parseJSON = String -> (Object -> Parser Config) -> Value -> Parser Config
forall a. String -> (Object -> Parser a) -> Value -> Parser a
A.withObject String
"Config" ((Object -> Parser Config) -> Value -> Parser Config)
-> (Object -> Parser Config) -> Value -> Parser Config
forall a b. (a -> b) -> a -> b
$ \Object
o ->
    String -> Vector Bookmark -> Config
Config
      (String -> Vector Bookmark -> Config)
-> Parser String -> Parser (Vector Bookmark -> Config)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe String)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"command" Parser (Maybe String) -> String -> Parser String
forall a. Parser (Maybe a) -> a -> Parser a
.!= String
defaultCommand
      Parser (Vector Bookmark -> Config)
-> Parser (Vector Bookmark) -> Parser Config
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Vector Bookmark)
forall a. FromJSON a => Object -> Key -> Parser a
.:  Key
"args"

------------------------------------------------------------------------------

-- | Bookmark definition
--
-- YAML attributes:
--
-- * @keyword@: bookmark keyword (string)
-- * @command@: command for this bookmark and children (string, optional)
-- * @url@: bookmark URL (string, optional)
-- * @query@: bookmark query definition ('Query', optional)
-- * @args@: child bookmarks (array of 'Bookmark', optional)
--
-- A command be set to override the top-level command, but this is generally
-- not done.  If a bookmark is selected and there is no URL, the first child
-- is processed.  Only one of @query@ and @args@ may be present.
--
-- @since 0.1.0.0
data Bookmark
  = Bookmark
    { Bookmark -> String
keyword     :: !Keyword
    , Bookmark -> Maybe String
mCommand    :: !(Maybe Command)
    , Bookmark -> Maybe String
mUrl        :: !(Maybe Url)
    , Bookmark -> Either Query (Vector Bookmark)
queryOrArgs :: !(Either Query (Vector Bookmark))
    }
  deriving Int -> Bookmark -> String -> String
[Bookmark] -> String -> String
Bookmark -> String
(Int -> Bookmark -> String -> String)
-> (Bookmark -> String)
-> ([Bookmark] -> String -> String)
-> Show Bookmark
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [Bookmark] -> String -> String
$cshowList :: [Bookmark] -> String -> String
show :: Bookmark -> String
$cshow :: Bookmark -> String
showsPrec :: Int -> Bookmark -> String -> String
$cshowsPrec :: Int -> Bookmark -> String -> String
Show

instance FromJSON Bookmark where
  parseJSON :: Value -> Parser Bookmark
parseJSON = String -> (Object -> Parser Bookmark) -> Value -> Parser Bookmark
forall a. String -> (Object -> Parser a) -> Value -> Parser a
A.withObject String
"Bookmark" ((Object -> Parser Bookmark) -> Value -> Parser Bookmark)
-> (Object -> Parser Bookmark) -> Value -> Parser Bookmark
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
    String
keyword  <- Value -> Parser String
parseToString (Value -> Parser String) -> Parser Value -> Parser String
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Object
o Object -> Key -> Parser Value
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"keyword"
    Maybe String
mCommand <- Object
o Object -> Key -> Parser (Maybe String)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"command"
    Maybe String
mUrl     <- Object
o Object -> Key -> Parser (Maybe String)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"url"
    Maybe Query
mQuery   <- Object
o Object -> Key -> Parser (Maybe Query)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"query"
    Maybe (Vector Bookmark)
mArgs    <- Object
o Object -> Key -> Parser (Maybe (Vector Bookmark))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"args"
    Either Query (Vector Bookmark)
queryOrArgs <- case (Maybe Query
mQuery, Maybe (Vector Bookmark)
mArgs) of
      (Maybe Query
Nothing,    Just Vector Bookmark
args) -> Either Query (Vector Bookmark)
-> Parser (Either Query (Vector Bookmark))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either Query (Vector Bookmark)
 -> Parser (Either Query (Vector Bookmark)))
-> Either Query (Vector Bookmark)
-> Parser (Either Query (Vector Bookmark))
forall a b. (a -> b) -> a -> b
$ Vector Bookmark -> Either Query (Vector Bookmark)
forall a b. b -> Either a b
Right Vector Bookmark
args
      (Just Query
query, Maybe (Vector Bookmark)
Nothing)   -> Either Query (Vector Bookmark)
-> Parser (Either Query (Vector Bookmark))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either Query (Vector Bookmark)
 -> Parser (Either Query (Vector Bookmark)))
-> Either Query (Vector Bookmark)
-> Parser (Either Query (Vector Bookmark))
forall a b. (a -> b) -> a -> b
$ Query -> Either Query (Vector Bookmark)
forall a b. a -> Either a b
Left Query
query
      (Maybe Query
Nothing,    Maybe (Vector Bookmark)
Nothing)   -> Either Query (Vector Bookmark)
-> Parser (Either Query (Vector Bookmark))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either Query (Vector Bookmark)
 -> Parser (Either Query (Vector Bookmark)))
-> Either Query (Vector Bookmark)
-> Parser (Either Query (Vector Bookmark))
forall a b. (a -> b) -> a -> b
$ Vector Bookmark -> Either Query (Vector Bookmark)
forall a b. b -> Either a b
Right Vector Bookmark
forall a. Vector a
V.empty
      (Just{},     Just{})    -> String -> Parser (Either Query (Vector Bookmark))
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Parser (Either Query (Vector Bookmark)))
-> String -> Parser (Either Query (Vector Bookmark))
forall a b. (a -> b) -> a -> b
$
        String
"bookmark has both query and args: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
keyword
    Bookmark -> Parser Bookmark
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bookmark :: String
-> Maybe String
-> Maybe String
-> Either Query (Vector Bookmark)
-> Bookmark
Bookmark{String
Maybe String
Either Query (Vector Bookmark)
queryOrArgs :: Either Query (Vector Bookmark)
mUrl :: Maybe String
mCommand :: Maybe String
keyword :: String
queryOrArgs :: Either Query (Vector Bookmark)
mUrl :: Maybe String
mCommand :: Maybe String
keyword :: String
..}

------------------------------------------------------------------------------

-- | Query definition
--
-- YAML attributes:
--
-- * @action@: URL (string)
-- * @parameter@: query parameter name (string, default: @q@)
-- * @hidden@: array of constant parameters ('Parameter')
--
-- @since 0.1.0.0
data Query
  = Query
    { Query -> String
action           :: !Url
    , Query -> String
parameter        :: !ParameterName
    , Query -> Vector Parameter
hiddenParameters :: !(Vector Parameter)
    }
  deriving Int -> Query -> String -> String
[Query] -> String -> String
Query -> String
(Int -> Query -> String -> String)
-> (Query -> String) -> ([Query] -> String -> String) -> Show Query
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [Query] -> String -> String
$cshowList :: [Query] -> String -> String
show :: Query -> String
$cshow :: Query -> String
showsPrec :: Int -> Query -> String -> String
$cshowsPrec :: Int -> Query -> String -> String
Show

instance FromJSON Query where
  parseJSON :: Value -> Parser Query
parseJSON = String -> (Object -> Parser Query) -> Value -> Parser Query
forall a. String -> (Object -> Parser a) -> Value -> Parser a
A.withObject String
"Query" ((Object -> Parser Query) -> Value -> Parser Query)
-> (Object -> Parser Query) -> Value -> Parser Query
forall a b. (a -> b) -> a -> b
$ \Object
o ->
    String -> String -> Vector Parameter -> Query
Query
      (String -> String -> Vector Parameter -> Query)
-> Parser String -> Parser (String -> Vector Parameter -> Query)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser String
forall a. FromJSON a => Object -> Key -> Parser a
.:  Key
"action"
      Parser (String -> Vector Parameter -> Query)
-> Parser String -> Parser (Vector Parameter -> Query)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe String)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"parameter" Parser (Maybe String) -> String -> Parser String
forall a. Parser (Maybe a) -> a -> Parser a
.!= String
defaultParameter
      Parser (Vector Parameter -> Query)
-> Parser (Vector Parameter) -> Parser Query
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe (Vector Parameter))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"hidden"    Parser (Maybe (Vector Parameter))
-> Vector Parameter -> Parser (Vector Parameter)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Vector Parameter
forall a. Vector a
V.empty

------------------------------------------------------------------------------

-- | HTTP GET parameter definition
--
-- YAML attributes:
--
-- * @name@: parameter name
-- * @value@: constant parameter value
--
-- @since 0.1.0.0
data Parameter
  = Parameter
    { Parameter -> String
name  :: !ParameterName
    , Parameter -> String
value :: !ParameterValue
    }
  deriving Int -> Parameter -> String -> String
[Parameter] -> String -> String
Parameter -> String
(Int -> Parameter -> String -> String)
-> (Parameter -> String)
-> ([Parameter] -> String -> String)
-> Show Parameter
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [Parameter] -> String -> String
$cshowList :: [Parameter] -> String -> String
show :: Parameter -> String
$cshow :: Parameter -> String
showsPrec :: Int -> Parameter -> String -> String
$cshowsPrec :: Int -> Parameter -> String -> String
Show

instance FromJSON Parameter where
  parseJSON :: Value -> Parser Parameter
parseJSON = String -> (Object -> Parser Parameter) -> Value -> Parser Parameter
forall a. String -> (Object -> Parser a) -> Value -> Parser a
A.withObject String
"Parameter" ((Object -> Parser Parameter) -> Value -> Parser Parameter)
-> (Object -> Parser Parameter) -> Value -> Parser Parameter
forall a b. (a -> b) -> a -> b
$ \Object
o ->
    String -> String -> Parameter
Parameter
      (String -> String -> Parameter)
-> Parser String -> Parser (String -> Parameter)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser String
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"name"
      Parser (String -> Parameter) -> Parser String -> Parser Parameter
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Value -> Parser String
parseToString (Value -> Parser String) -> Parser Value -> Parser String
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Object
o Object -> Key -> Parser Value
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"value")

-- | Encode an HTTP GET parameter
--
-- Spaces are transformed to plus characters, and other reserved characters
-- are escaped.
encodeParameter :: Parameter -> String
encodeParameter :: Parameter -> String
encodeParameter Parameter{String
value :: String
name :: String
value :: Parameter -> String
name :: Parameter -> String
..} = String -> String
encodePart String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
encodePart String
value
  where
    encodePart :: String -> String
    encodePart :: String -> String
encodePart
      = (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map (\Char
c -> if Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ' then Char
'+' else Char
c)
      (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> String
URI.escapeURIString (Bool -> Bool -> Bool
(||) (Bool -> Bool -> Bool) -> (Char -> Bool) -> Char -> Bool -> Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Char -> Bool
URI.isUnreserved (Char -> Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' '))

------------------------------------------------------------------------------

-- | Process specification
--
-- @since 0.1.0.0
data Proc
  = Proc
    { Proc -> String
command   :: !Command
    , Proc -> [String]
arguments :: ![Argument]
    }
  deriving (Proc -> Proc -> Bool
(Proc -> Proc -> Bool) -> (Proc -> Proc -> Bool) -> Eq Proc
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Proc -> Proc -> Bool
$c/= :: Proc -> Proc -> Bool
== :: Proc -> Proc -> Bool
$c== :: Proc -> Proc -> Bool
Eq, Int -> Proc -> String -> String
[Proc] -> String -> String
Proc -> String
(Int -> Proc -> String -> String)
-> (Proc -> String) -> ([Proc] -> String -> String) -> Show Proc
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [Proc] -> String -> String
$cshowList :: [Proc] -> String -> String
show :: Proc -> String
$cshow :: Proc -> String
showsPrec :: Int -> Proc -> String -> String
$cshowsPrec :: Int -> Proc -> String -> String
Show)

------------------------------------------------------------------------------
-- $API

-- | Determine the process to execute for the given config and CLI arguments
--
-- @since 0.1.0.0
run
  :: Config
  -> [Argument]
  -> (Either Error Proc, [Trace])
run :: Config -> [String] -> (Either String Proc, [String])
run Config{String
Vector Bookmark
configArgs :: Vector Bookmark
configCommand :: String
configArgs :: Config -> Vector Bookmark
configCommand :: Config -> String
..} [String]
cliArgs = (DList String -> [String])
-> (Either String Proc, DList String)
-> (Either String Proc, [String])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap DList String -> [String]
forall a. DList a -> [a]
DList.toList ((Either String Proc, DList String)
 -> (Either String Proc, [String]))
-> (Writer (DList String) (Either String Proc)
    -> (Either String Proc, DList String))
-> Writer (DList String) (Either String Proc)
-> (Either String Proc, [String])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Writer (DList String) (Either String Proc)
-> (Either String Proc, DList String)
forall w a. Writer w a -> (a, w)
runWriter (Writer (DList String) (Either String Proc)
 -> (Either String Proc, [String]))
-> Writer (DList String) (Either String Proc)
-> (Either String Proc, [String])
forall a b. (a -> b) -> a -> b
$ do
    String -> Writer (DList String) ()
trace (String -> Writer (DList String) ())
-> String -> Writer (DList String) ()
forall a b. (a -> b) -> a -> b
$ String -> String
formatCommand String
configCommand
    String
-> Vector Bookmark
-> [String]
-> Writer (DList String) (Either String Proc)
loop String
configCommand Vector Bookmark
configArgs [String]
cliArgs
  where
    loop
      :: Command
      -> Vector Bookmark
      -> [Argument]
      -> Writer (DList Trace) (Either Error Proc)
    loop :: String
-> Vector Bookmark
-> [String]
-> Writer (DList String) (Either String Proc)
loop String
cmd Vector Bookmark
bms (String
arg:[String]
args) = case (Bookmark -> Bool) -> Vector Bookmark -> Maybe Bookmark
forall a. (a -> Bool) -> Vector a -> Maybe a
V.find (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isPrefixOf String
arg (String -> Bool) -> (Bookmark -> String) -> Bookmark -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bookmark -> String
keyword) Vector Bookmark
bms of
      Just Bookmark
bm -> do
        String -> Writer (DList String) ()
trace (String -> Writer (DList String) ())
-> String -> Writer (DList String) ()
forall a b. (a -> b) -> a -> b
$ Bookmark -> String
formatBookmark Bookmark
bm
        case Bookmark -> Either Query (Vector Bookmark)
queryOrArgs Bookmark
bm of
          Left Query
query
            | [String] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [String]
args -> case Bookmark -> Maybe String
mUrl Bookmark
bm of
                Just String
url -> String -> String -> Writer (DList String) (Either String Proc)
openUrl (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
cmd (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Bookmark -> Maybe String
mCommand Bookmark
bm) String
url
                Maybe String
Nothing -> String -> Writer (DList String) (Either String Proc)
returnError (String -> Writer (DList String) (Either String Proc))
-> String -> Writer (DList String) (Either String Proc)
forall a b. (a -> b) -> a -> b
$ String
"no query for " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Bookmark -> String
keyword Bookmark
bm
            | Bool
otherwise -> String
-> Query -> [String] -> Writer (DList String) (Either String Proc)
openQuery (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
cmd (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Bookmark -> Maybe String
mCommand Bookmark
bm) Query
query [String]
args
          Right Vector Bookmark
bms'
            | [String] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [String]
args -> case Bookmark -> Maybe String
mUrl Bookmark
bm of
                Just String
url -> String -> String -> Writer (DList String) (Either String Proc)
openUrl (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
cmd (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Bookmark -> Maybe String
mCommand Bookmark
bm) String
url
                Maybe String
Nothing  -> case Vector Bookmark
bms' Vector Bookmark -> Int -> Maybe Bookmark
forall a. Vector a -> Int -> Maybe a
V.!? Int
0 of
                  Just Bookmark
bm' ->
                    String
-> Vector Bookmark
-> [String]
-> Writer (DList String) (Either String Proc)
loop (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
cmd (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Bookmark -> Maybe String
mCommand Bookmark
bm) Vector Bookmark
bms' [Bookmark -> String
keyword Bookmark
bm']
                  Maybe Bookmark
Nothing -> String -> Writer (DList String) (Either String Proc)
returnError (String -> Writer (DList String) (Either String Proc))
-> String -> Writer (DList String) (Either String Proc)
forall a b. (a -> b) -> a -> b
$ String
"no URL for " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Bookmark -> String
keyword Bookmark
bm
            | Bool
otherwise -> String
-> Vector Bookmark
-> [String]
-> Writer (DList String) (Either String Proc)
loop (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
cmd (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Bookmark -> Maybe String
mCommand Bookmark
bm) Vector Bookmark
bms' [String]
args
      Maybe Bookmark
Nothing -> String -> Writer (DList String) (Either String Proc)
returnError (String -> Writer (DList String) (Either String Proc))
-> String -> Writer (DList String) (Either String Proc)
forall a b. (a -> b) -> a -> b
$ String
"unknown argument: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
arg
    loop String
_cmd Vector Bookmark
_bms [] = String -> Writer (DList String) (Either String Proc)
returnError String
"no arguments"

    returnError :: Error -> Writer (DList Trace) (Either Error Proc)
    returnError :: String -> Writer (DList String) (Either String Proc)
returnError = Either String Proc -> Writer (DList String) (Either String Proc)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Proc -> Writer (DList String) (Either String Proc))
-> (String -> Either String Proc)
-> String
-> Writer (DList String) (Either String Proc)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Either String Proc
forall a b. a -> Either a b
Left

    openUrl :: Command -> Url -> Writer (DList Trace) (Either Error Proc)
    openUrl :: String -> String -> Writer (DList String) (Either String Proc)
openUrl String
cmd String
url = do
      String -> Writer (DList String) ()
trace (String -> Writer (DList String) ())
-> String -> Writer (DList String) ()
forall a b. (a -> b) -> a -> b
$ [String] -> String
unwords [String
cmd, String
url]
      Either String Proc -> Writer (DList String) (Either String Proc)
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String Proc -> Writer (DList String) (Either String Proc))
-> (Proc -> Either String Proc)
-> Proc
-> Writer (DList String) (Either String Proc)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Proc -> Either String Proc
forall a b. b -> Either a b
Right (Proc -> Writer (DList String) (Either String Proc))
-> Proc -> Writer (DList String) (Either String Proc)
forall a b. (a -> b) -> a -> b
$ String -> [String] -> Proc
Proc String
cmd [String
url]

    openQuery
      :: Command
      -> Query
      -> [Argument]
      -> Writer (DList Trace) (Either Error Proc)
    openQuery :: String
-> Query -> [String] -> Writer (DList String) (Either String Proc)
openQuery String
cmd Query{String
Vector Parameter
hiddenParameters :: Vector Parameter
parameter :: String
action :: String
hiddenParameters :: Query -> Vector Parameter
parameter :: Query -> String
action :: Query -> String
..} [String]
args
      = String -> String -> Writer (DList String) (Either String Proc)
openUrl String
cmd
      (String -> Writer (DList String) (Either String Proc))
-> ([Parameter] -> String)
-> [Parameter]
-> Writer (DList String) (Either String Proc)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String
action String -> String -> String
forall a. [a] -> [a] -> [a]
++)
      (String -> String)
-> ([Parameter] -> String) -> [Parameter] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char
'?' Char -> String -> String
forall a. a -> [a] -> [a]
:)
      (String -> String)
-> ([Parameter] -> String) -> [Parameter] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"&"
      ([String] -> String)
-> ([Parameter] -> [String]) -> [Parameter] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Parameter -> String) -> [Parameter] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Parameter -> String
encodeParameter
      ([Parameter] -> Writer (DList String) (Either String Proc))
-> [Parameter] -> Writer (DList String) (Either String Proc)
forall a b. (a -> b) -> a -> b
$ String -> String -> Parameter
Parameter String
parameter ([String] -> String
unwords [String]
args) Parameter -> [Parameter] -> [Parameter]
forall a. a -> [a] -> [a]
: Vector Parameter -> [Parameter]
forall a. Vector a -> [a]
V.toList Vector Parameter
hiddenParameters

    trace :: Trace -> Writer (DList Trace) ()
    trace :: String -> Writer (DList String) ()
trace = DList String -> Writer (DList String) ()
forall (m :: * -> *) w. Monad m => w -> WriterT w m ()
tell (DList String -> Writer (DList String) ())
-> (String -> DList String) -> String -> Writer (DList String) ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> DList String
forall a. a -> DList a
DList.singleton

    formatCommand :: Command -> Trace
    formatCommand :: String -> String
formatCommand = (Char
'[' Char -> String -> String
forall a. a -> [a] -> [a]
:) (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"]")

    formatKeyword :: Keyword -> Trace
    formatKeyword :: String -> String
formatKeyword = (Char
'<' Char -> String -> String
forall a. a -> [a] -> [a]
:) (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
">")

    formatBookmark :: Bookmark -> Trace
    formatBookmark :: Bookmark -> String
formatBookmark Bookmark{String
Maybe String
Either Query (Vector Bookmark)
queryOrArgs :: Either Query (Vector Bookmark)
mUrl :: Maybe String
mCommand :: Maybe String
keyword :: String
queryOrArgs :: Bookmark -> Either Query (Vector Bookmark)
mUrl :: Bookmark -> Maybe String
mCommand :: Bookmark -> Maybe String
keyword :: Bookmark -> String
..} = case Maybe String
mCommand of
      Just String
command -> [String] -> String
unwords [String -> String
formatKeyword String
keyword, String -> String
formatCommand String
command]
      Maybe String
Nothing      -> String -> String
formatKeyword String
keyword

------------------------------------------------------------------------------

-- | Get CLI completion options
--
-- @since 0.1.0.0
getCompletion
  :: Config
  -> [Argument]  -- ^ current CLI arguments, last one being completed
  -> [Argument]  -- ^ completion options
getCompletion :: Config -> [String] -> [String]
getCompletion Config{String
Vector Bookmark
configArgs :: Vector Bookmark
configCommand :: String
configArgs :: Config -> Vector Bookmark
configCommand :: Config -> String
..} = Vector Bookmark -> [String] -> [String]
loop Vector Bookmark
configArgs
  where
    loop :: Vector Bookmark -> [Argument] -> [Argument]
    loop :: Vector Bookmark -> [String] -> [String]
loop Vector Bookmark
bms [String
arg] = (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isPrefixOf String
arg) ([String] -> [String])
-> ([Bookmark] -> [String]) -> [Bookmark] -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Bookmark -> String) -> [Bookmark] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Bookmark -> String
keyword ([Bookmark] -> [String]) -> [Bookmark] -> [String]
forall a b. (a -> b) -> a -> b
$ Vector Bookmark -> [Bookmark]
forall a. Vector a -> [a]
V.toList Vector Bookmark
bms
    loop Vector Bookmark
bms (String
arg:[String]
args) = case (Bookmark -> Bool) -> Vector Bookmark -> Maybe Bookmark
forall a. (a -> Bool) -> Vector a -> Maybe a
V.find (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isPrefixOf String
arg (String -> Bool) -> (Bookmark -> String) -> Bookmark -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bookmark -> String
keyword) Vector Bookmark
bms of
      Just Bookmark
bm -> case Bookmark -> Either Query (Vector Bookmark)
queryOrArgs Bookmark
bm of
        Left{} -> []
        Right Vector Bookmark
bms' -> Vector Bookmark -> [String] -> [String]
loop Vector Bookmark
bms' [String]
args
      Maybe Bookmark
Nothing -> []
    loop Vector Bookmark
_bms [] = []

------------------------------------------------------------------------------
-- $Internal

-- | Parse any scalar value as a string
--
-- Strings, numbers, booleans, and null are parsed as a string.  Arrays and
-- objects result in an error.
parseToString :: A.Value -> AT.Parser String
parseToString :: Value -> Parser String
parseToString = \case
    (A.String Text
t)  -> String -> Parser String
forall (f :: * -> *) a. Applicative f => a -> f a
pure (String -> Parser String) -> String -> Parser String
forall a b. (a -> b) -> a -> b
$ Text -> String
T.unpack Text
t
    (A.Number Scientific
n)  -> String -> Parser String
forall (f :: * -> *) a. Applicative f => a -> f a
pure (String -> Parser String)
-> (Either Double Integer -> String)
-> Either Double Integer
-> Parser String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Double -> String)
-> (Integer -> String) -> Either Double Integer -> String
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Show Double => Double -> String
forall a. Show a => a -> String
show @Double) (Show Integer => Integer -> String
forall a. Show a => a -> String
show @Integer) (Either Double Integer -> Parser String)
-> Either Double Integer -> Parser String
forall a b. (a -> b) -> a -> b
$
      Scientific -> Either Double Integer
forall r i. (RealFloat r, Integral i) => Scientific -> Either r i
Sci.floatingOrInteger Scientific
n
    (A.Bool Bool
b)    -> String -> Parser String
forall (f :: * -> *) a. Applicative f => a -> f a
pure (String -> Parser String) -> String -> Parser String
forall a b. (a -> b) -> a -> b
$ if Bool
b then String
"true" else String
"false"
    Value
A.Null        -> String -> Parser String
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
"null"
    A.Array{}     -> String -> Parser String
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"unexpected array"
    A.Object{}    -> String -> Parser String
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"unexpected object"