module Main (main) where import Data.Gibberish import Data.Gibberish.Formatting qualified as Fmt import Data.Text (Text ()) import Data.Text qualified as Text import Data.Text.IO qualified as Text import Data.Version (showVersion) import Options.Applicative hiding (columns) import Options.Applicative.Help.Pretty import Paths_gibberish (version) import System.Environment (getArgs) import Prelude hiding (Word ()) termLen :: Int termLen = 80 termHeight :: Int termHeight = 10 data Options = Options { -- | Options that are always used optCommon :: CommonOpts, -- | Options that only apply to a specific types optType :: Either WordOpts PhraseOpts } deriving stock (Eq, Show) data CommonOpts = CommonOpts { -- | Include capitals? optCapitals :: !Bool, -- | Include numerals? optDigits :: !Bool, -- | Include special characters? optSpecials :: !Bool, -- | How many passwords/phrases to generate optNumber :: !(Maybe Int), -- | The dictionary to use optLanguage :: !LanguageOpt } deriving stock (Eq, Show) data WordOpts = WordOpts {optLength :: Int} deriving stock (Eq, Show) data PhraseOpts = PhraseOpts { optMinLength :: Int, optMaxLength :: Int } deriving stock (Eq, Show) data LanguageOpt = OptEnglish | OptSpanish | OptLangCustom !TrigraphConfig deriving stock (Eq, Show) data PassType = Passphrase | Password deriving stock (Eq, Show) main :: IO () main = run =<< execParser' opts where opts = info (optsParser <**> parseVersion <**> helper) mods mods = briefDesc <> progDesc desc desc = "Generates pronounceable passwords that are easy-to-remember and\ \ hard-to-guess." run :: Options -> IO () run (Options {..}) = Text.putStrLn =<< run' optType =<< getStdGen where run' (Left w) = passwords optCommon w run' (Right p) = passphrases optCommon p passwords :: RandomGen gen => CommonOpts -> WordOpts -> gen -> IO Text passwords (CommonOpts {..}) (WordOpts {..}) gen = do trigraph <- liftIO $ loadTrigraph (toLanguage optLanguage) let genOpts = GenPasswordOpts { woptsCapitals = optCapitals, woptsDigits = optDigits, woptsSpecials = optSpecials, woptsTrigraph = trigraph, woptsLength = optLength } formatOpts = Fmt.FormatOpts { optMaxLen = Fmt.MaxLen termLen, optMaxHeight = Fmt.MaxHeight termHeight, optSeparator = Fmt.Separator " ", optExactWords = Fmt.ExactNumberWords <$> optNumber } (res, _) = usingPass gen (genPasswords genOpts) pure (Fmt.formatWords formatOpts res) passphrases :: RandomGen gen => CommonOpts -> PhraseOpts -> gen -> IO Text passphrases (CommonOpts {..}) (PhraseOpts {..}) gen = do trigraph <- liftIO $ loadTrigraph English let genOpts = GenPassphraseOpts { poptsCapitals = optCapitals, poptsDigits = optDigits, poptsSpecials = optSpecials, poptsTrigraph = trigraph, poptsMinLength = optMinLength, poptsMaxLength = optMaxLength } formatOpts = Fmt.FormatOpts { optMaxLen = Fmt.MaxLen termLen, optMaxHeight = Fmt.MaxHeight termHeight, optSeparator = Fmt.Separator " ", optExactWords = Fmt.ExactNumberWords <$> optNumber } (res, _) = usingPass gen (genPassphrase genOpts) pure (Fmt.formatWords formatOpts res) execParser' :: ParserInfo a -> IO a execParser' info' = execParserPure defaultPrefs info' <$> getArgs >>= handleParseResult . overFailure' overFailure' :: ParserResult a -> ParserResult a overFailure' = overFailure $ \help' -> help' {helpUsage = pure usage} usage :: Doc usage = hsep [ pretty ("Usage:" :: Text), align $ vsep [ "gibber [option...] length", "gibber --passphrase [option...] min-length max-length" ] ] optsParser :: Parser Options optsParser = parseOptions where parseOptions = Options <$> parseCommonOpts <*> parseTypeOpts parseCommonOpts :: Parser CommonOpts parseCommonOpts = CommonOpts <$> parseCapitals <*> parseDigits <*> parseSpecials <*> parseNumber <*> parseLanguage parseCapitals :: Parser Bool parseCapitals = switch $ long "capitals" <> short 'c' <> help "Include at least one capital letter" parseDigits :: Parser Bool parseDigits = switch $ long "digits" <> short 'd' <> help "Include numerals" parseSpecials :: Parser Bool parseSpecials = switch $ long "symbols" <> short 's' <> help "Include special characters" parseNumber :: Parser (Maybe Int) parseNumber = optional $ option auto $ long "number" <> short 'n' <> metavar "NUMBER" <> help "The number of passwords to generate" parseLanguage :: Parser LanguageOpt parseLanguage = (OptLangCustom . TrigraphConfig <$> parseTrigraph) <|> parseSpanish <|> parseEnglish where parseTrigraph = option str $ long "trigraph" <> short 't' <> help "Use a custom trigraph config" parseSpanish = flag' OptSpanish $ long "spanish" <> short 'a' <> help "Use spanish dictionary" parseEnglish = flag OptEnglish OptEnglish $ long "english" <> short 'e' <> help "Use english dictionary [default]" parseTypeOpts :: Parser (Either WordOpts PhraseOpts) parseTypeOpts = (Left <$> parseWordOpts) <|> (Right <$> parsePhraseOpts) parseWordOpts :: Parser WordOpts parseWordOpts = (WordOpts <$> parseLength) parsePhraseOpts :: Parser PhraseOpts parsePhraseOpts = parseTypePhrase *> (PhraseOpts <$> parseMinLength <*> parseMaxLength) parseTypePhrase :: Parser PassType parseTypePhrase = flag' Passphrase $ long "passphrase" <> short 'p' <> help "Generate passphrases instead of passwords" parseVersion :: Parser (a -> a) parseVersion = infoOption (Text.unpack showVersion') $ long "version" <> short 'v' <> help "Show version information" parseLength :: Parser Int parseLength = argument auto (metavar "length") parseMinLength :: Parser Int parseMinLength = argument auto (metavar "min-length") parseMaxLength :: Parser Int parseMaxLength = argument auto $ metavar "max-length" <> value 10 showVersion' :: Text showVersion' = name' <> " version " <> version' where version' = Text.pack (showVersion version) name' = "Gibberish" toLanguage :: LanguageOpt -> Language toLanguage OptEnglish = English toLanguage OptSpanish = Spanish toLanguage (OptLangCustom file) = CustomTrigraph file