{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE NoImplicitPrelude #-} module Neuron.CLI.Types ( -- * CLI App (..), Command (..), NewCommand (..), SearchBy (..), SearchCommand (..), OpenCommand (..), QueryCommand (..), RibConfig (..), commandParser, ) where import Data.Default (def) import Data.Some import Data.TagTree (mkTagPattern) import Data.Time import Data.Time.DateMayTime ( DateMayTime, formatDateMayTime, mkDateMayTime, parseDateMayTime, ) import Neuron.Reader.Type (ZettelFormat) import qualified Neuron.Web.Route as R import qualified Neuron.Zettelkasten.Connection as C import Neuron.Zettelkasten.ID (ZettelID, parseZettelID) import Neuron.Zettelkasten.ID.Scheme (IDScheme (..)) import qualified Neuron.Zettelkasten.Query.Error as Q import Neuron.Zettelkasten.Query.Graph as Q import qualified Neuron.Zettelkasten.Query.Parser as Q import Neuron.Zettelkasten.Zettel as Q import Options.Applicative import Relude import qualified Rib.Cli import qualified Text.URI as URI data App = App { notesDir :: FilePath, cmd :: Command } data NewCommand = NewCommand { format :: Maybe ZettelFormat, date :: DateMayTime, idScheme :: Some IDScheme, edit :: Bool } deriving (Eq, Show) data SearchCommand = SearchCommand { searchBy :: SearchBy, searchEdit :: Bool } deriving (Eq, Show) data SearchBy = SearchByTitle | SearchByContent deriving (Eq, Show) data OpenCommand = OpenCommand { route :: Some R.Route } deriving (Eq, Show) data QueryCommand = QueryCommand { -- Use cache instead of building the zettelkasten from scratch cached :: Bool, query :: Either (Some Q.ZettelQuery) (Some Q.GraphQuery) } deriving (Eq, Show) data Command = -- | Create a new zettel file New NewCommand | -- | Open the locally generated Zettelkasten Open OpenCommand | -- | Search a zettel by title Search SearchCommand | -- | Run a query against the Zettelkasten Query QueryCommand | -- | Delegate to Rib's command parser Rib RibConfig data RibConfig = RibConfig { ribOutputDir :: Maybe FilePath, ribWatch :: Bool, ribServe :: Maybe (Text, Int), ribQuiet :: Bool, ribShakeDbDir :: Maybe FilePath } deriving (Eq, Show) -- | optparse-applicative parser for neuron CLI commandParser :: FilePath -> LocalTime -> Parser App commandParser defaultNotesDir now = do notesDir <- option Rib.Cli.directoryReader ( short 'd' <> metavar "PATH" <> value defaultNotesDir <> help "Run as if neuron was started in PATH instead of the current working directory" ) cmd <- cmdParser pure $ App {..} where cmdParser = hsubparser $ mconcat [ command "new" $ info newCommand $ progDesc "Create a new zettel", command "open" $ info openCommand $ progDesc "Open the locally generated Zettelkasten website", command "search" $ info searchCommand $ progDesc "Search zettels and print the matching filepath", command "query" $ info queryCommand $ progDesc "Run a query against the zettelkasten", command "rib" $ info ribCommand $ progDesc "Generate static site via rib" ] newCommand = do idScheme <- fmap (maybe (Some IDSchemeHash) (Some . IDSchemeCustom)) $ optional $ strArgument (metavar "TITLEID" <> help "Custom (title) ID to use; otherwise random ID will be generated") format <- optional $ option auto $ metavar "FORMAT" <> short 'f' <> long "format" <> help "The document format of the new zettel" edit <- switch (long "edit" <> short 'e' <> help "Open the newly-created zettel in $EDITOR") dateParam <- option dateReader $ long "date" <> metavar "DATE/TIME" <> value (mkDateMayTime $ Right now) <> showDefaultWith (toString . formatDateMayTime) <> help "Zettel date/time" pure $ New $ NewCommand format dateParam idScheme edit openCommand = do fmap Open $ fmap (const $ OpenCommand $ Some $ R.Route_ZIndex) (switch (long "zindex" <> help "Open z-index")) <|> fmap (const $ OpenCommand $ Some $ R.Route_Search Nothing) (switch (long "search" <> help "Open the search page")) <|> fmap (OpenCommand . Some . R.Route_Zettel) (option zettelIDReader (long "id" <> help "Open the zettel HTML page" <> metavar "ID")) queryCommand = do cached <- switch (long "cached" <> help "Use cached zettelkasten graph (faster)") query <- fmap Left ( fmap (Some . flip Q.ZettelQuery_ZettelByID connDummy) (option zettelIDReader (long "id")) <|> fmap (\x -> Some $ Q.ZettelQuery_ZettelsByTag x connDummy def) (many (mkTagPattern <$> option str (long "tag" <> short 't'))) <|> option queryReader (long "uri" <> short 'u') ) <|> fmap Right ( fmap (const $ Some $ Q.GraphQuery_Id) ( switch $ long "graph" <> help "Get the entire zettelkasten graph as JSON" ) <|> fmap (Some . Q.GraphQuery_BacklinksOf Nothing) ( option zettelIDReader ( long "backlinks-of" <> help "Get backlinks to the given zettel ID" <> metavar "ID" ) ) <|> fmap (Some . Q.GraphQuery_BacklinksOf (Just C.Folgezettel)) ( option zettelIDReader ( long "uplinks-of" <> help "Get uplinks to the given zettel ID" <> metavar "ID" ) ) ) pure $ Query $ QueryCommand {..} searchCommand = do searchBy <- bool SearchByTitle SearchByContent <$> switch (long "full-text" <> short 'a' <> help "Full-text search") edit <- switch (long "edit" <> short 'e' <> help "Open the matching zettel in $EDITOR") pure $ Search $ SearchCommand searchBy edit ribCommand = fmap Rib $ do let ribQuiet = False ribShakeDbDir = Nothing ribOutputDir <- optional $ option Rib.Cli.directoryReader ( long "output-dir" <> short 'o' <> metavar "OUTPUTDIR" <> showDefault <> help "The directory where HTML will be generated" ) ribWatch <- Rib.Cli.watchOption ribServe <- Rib.Cli.serveOption pure RibConfig {..} zettelIDReader :: ReadM ZettelID zettelIDReader = eitherReader $ first show . parseZettelID . toText queryReader :: ReadM (Some Q.ZettelQuery) queryReader = eitherReader $ \(toText -> s) -> case URI.mkURI s of Right uri -> either (Left . toString . Q.showQueryParseError) (maybe (Left "Unsupported query") Right) $ Q.queryFromURI connDummy uri Left e -> Left $ displayException e dateReader :: ReadM DateMayTime dateReader = maybeReader (parseDateMayTime . toText) -- We don't care about connections in the CLI, but the query requires one - -- so pass a dummy value. connDummy = C.OrdinaryConnection