packages feed

glirc 2.20.5 → 2.20.6

raw patch · 11 files changed

+224/−434 lines, 11 filesdep +config-schemadep +semigroupoidsPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: config-schema, semigroupoids

API changes (from Hackage documentation)

- Client.Configuration.Colors: parseAttr :: Value -> ConfigParser Attr
- Client.Configuration.Colors: parseColor :: Value -> ConfigParser Color
- Config.FromConfig: class FromConfig a
- Config.FromConfig: data ConfigParser a
- Config.FromConfig: data SectionParser a
- Config.FromConfig: decodeConfig :: FromConfig a => Value -> Either Text a
- Config.FromConfig: extendLoc :: Text -> ConfigParser a -> ConfigParser a
- Config.FromConfig: failure :: Text -> ConfigParser a
- Config.FromConfig: instance Config.FromConfig.FromConfig Config.Value.Atom
- Config.FromConfig: instance Config.FromConfig.FromConfig Data.Text.Internal.Text
- Config.FromConfig: instance Config.FromConfig.FromConfig GHC.Integer.Type.Integer
- Config.FromConfig: instance Config.FromConfig.FromConfig GHC.Types.Bool
- Config.FromConfig: instance Config.FromConfig.FromConfig GHC.Types.Int
- Config.FromConfig: instance Config.FromConfig.FromConfig a => Config.FromConfig.FromConfig [a]
- Config.FromConfig: instance GHC.Base.Applicative Config.FromConfig.ConfigParser
- Config.FromConfig: instance GHC.Base.Applicative Config.FromConfig.SectionParser
- Config.FromConfig: instance GHC.Base.Functor Config.FromConfig.ConfigParser
- Config.FromConfig: instance GHC.Base.Functor Config.FromConfig.SectionParser
- Config.FromConfig: instance GHC.Base.Monad Config.FromConfig.ConfigParser
- Config.FromConfig: instance GHC.Base.Monad Config.FromConfig.SectionParser
- Config.FromConfig: instance GHC.Real.Integral a => Config.FromConfig.FromConfig (GHC.Real.Ratio a)
- Config.FromConfig: liftConfigParser :: ConfigParser a -> SectionParser a
- Config.FromConfig: parseConfig :: FromConfig a => Value -> ConfigParser a
- Config.FromConfig: parseList :: (Value -> ConfigParser a) -> Value -> ConfigParser [a]
- Config.FromConfig: parseSections :: SectionParser a -> Value -> ConfigParser a
- Config.FromConfig: parseSectionsWith :: (a -> Text -> Value -> ConfigParser a) -> a -> Value -> ConfigParser a
- Config.FromConfig: runConfigParser :: ConfigParser a -> Either Text a
- Config.FromConfig: sectionOpt :: FromConfig a => Text -> SectionParser (Maybe a)
- Config.FromConfig: sectionOptWith :: (Value -> ConfigParser a) -> Text -> SectionParser (Maybe a)
- Config.FromConfig: sectionReq :: FromConfig a => Text -> SectionParser a
- Config.FromConfig: sectionReqWith :: (Value -> ConfigParser a) -> Text -> SectionParser a
+ Client.Configuration: configurationSpec :: ValueSpecs (Maybe FilePath -> ServerSettings -> Configuration)
+ Client.Configuration.Colors: attrSpec :: ValueSpecs Attr
+ Client.Configuration.Colors: colorSpec :: ValueSpecs Color
+ Client.Options: [_optShowConfigFormat] :: Options -> Bool
- Client.Options: Options :: Maybe FilePath -> [Text] -> Bool -> Bool -> Bool -> Bool -> Options
+ Client.Options: Options :: Maybe FilePath -> [Text] -> Bool -> Bool -> Bool -> Bool -> Bool -> Options
- Client.State.EditBox: class HasLine c_aLxk where pos = (.) line pos text = (.) line text
+ Client.State.EditBox: class HasLine c_atSp where pos = (.) line pos text = (.) line text
- Client.State.EditBox: line :: HasLine c_aLxk => Lens' c_aLxk Line
+ Client.State.EditBox: line :: HasLine c_atSp => Lens' c_atSp Line
- Client.State.EditBox: pos :: HasLine c_aLxk => Lens' c_aLxk Int
+ Client.State.EditBox: pos :: HasLine c_atSp => Lens' c_atSp Int
- Client.State.EditBox: text :: HasLine c_aLxk => Lens' c_aLxk String
+ Client.State.EditBox: text :: HasLine c_atSp => Lens' c_atSp String
- Client.State.EditBox.Content: class HasLine c_aLxk where pos = (.) line pos text = (.) line text
+ Client.State.EditBox.Content: class HasLine c_atSp where pos = (.) line pos text = (.) line text
- Client.State.EditBox.Content: line :: HasLine c_aLxk => Lens' c_aLxk Line
+ Client.State.EditBox.Content: line :: HasLine c_atSp => Lens' c_atSp Line
- Client.State.EditBox.Content: pos :: HasLine c_aLxk => Lens' c_aLxk Int
+ Client.State.EditBox.Content: pos :: HasLine c_atSp => Lens' c_atSp Int
- Client.State.EditBox.Content: text :: HasLine c_aLxk => Lens' c_aLxk String
+ Client.State.EditBox.Content: text :: HasLine c_atSp => Lens' c_atSp String

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Revision history for glirc2 +## 2.20.6++* Switch to new `config-schema` package for configuration file loading.+* Add `--config-format` flag to executable to show configuration file format.+ ## 2.20.5  * Add line indicating message since previous time window was focused.
exec/Main.hs view
@@ -57,7 +57,7 @@        Left (ConfigurationParseFailed e) ->          report "Failed to parse configuration:" e        Left (ConfigurationMalformed e) ->-         report "Configuration malformed: " e+         report "Configuration malformed (try --config-format): " e   where     report problem msg =       do hPutStrLn stderr problem
glirc.cabal view
@@ -1,5 +1,5 @@ name:                glirc-version:             2.20.5+version:             2.20.6 synopsis:            Console IRC client description:         Console IRC client                      .@@ -23,9 +23,9 @@ tested-with:         GHC==8.0.2  custom-setup-  setup-depends: base  >=4.9  && <4.11,-                 filepath >1.4 && <1.5,-                 Cabal >=1.24 && <3+  setup-depends: base     >=4.9  && <4.11,+                 filepath >=1.4  && <1.5,+                 Cabal    >=1.24 && <3  source-repository head   type: git@@ -111,7 +111,6 @@                        Client.View.UrlSelection                        Client.View.UserList                        Client.View.Windows-                       Config.FromConfig    other-modules:       LensUtils                        StrictUnit@@ -125,6 +124,7 @@                        bytestring           >=0.10.8 && <0.11,                        base64-bytestring    >=1.0.0.1 && <1.1,                        config-value         >=0.5    && <0.6,+                       config-schema        >=0.1    && <0.2,                        containers           >=0.5.7  && <0.6,                        directory            >=1.2.6  && <1.4,                        filepath             >=1.4.1  && <1.5,@@ -137,6 +137,7 @@                        network              >=2.6.2  && <2.7,                        process              >=1.4.2  && <1.7,                        regex-tdfa           >=1.2    && <1.3,+                       semigroupoids        >=5.2    && <5.3,                        socks                >=0.5.5  && <0.6,                        split                >=0.2    && <0.3,                        stm                  >=2.4    && <2.5,
src/Client/Commands.hs view
@@ -1048,7 +1048,7 @@ -- | Tab completion for @/splits-@. This completes only from the list of active -- entries in the splits list. tabActiveSplits :: Bool -> ClientCommand String-tabActiveSplits isReversed st rest =+tabActiveSplits isReversed st _ =   simpleTabCompletion plainWordCompleteMode [] completions isReversed st   where     completions = renderFocus <$> view clientExtraFocus st
src/Client/Configuration.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE TemplateHaskell   #-}+{-# LANGUAGE ApplicativeDo     #-}  {-| Module      : Client.Configuration@@ -40,6 +41,9 @@    -- * Resolving paths   , resolveConfigurationPath++  -- * Specification+  , configurationSpec   ) where  import           Client.Commands.Interpolation@@ -49,11 +53,12 @@ import           Client.Configuration.ServerSettings import           Client.Image.Palette import           Config-import           Config.FromConfig+import           Config.Schema+import           Control.Applicative import           Control.Exception import           Control.Lens                        hiding (List)-import           Control.Monad-import           Data.Foldable                       (for_)+import           Data.Foldable                       (find, foldl')+import           Data.Functor.Alt                    ((<!>)) import           Data.HashMap.Strict                 (HashMap) import qualified Data.HashMap.Strict                 as HashMap import           Data.HashSet                        (HashSet)@@ -61,6 +66,7 @@ import           Data.List.NonEmpty                  (NonEmpty) import qualified Data.List.NonEmpty                  as NonEmpty import           Data.Maybe+import           Data.Monoid                         ((<>)) import           Data.Text                           (Text) import qualified Data.Text                           as Text import qualified Data.Text.IO                        as Text@@ -179,158 +185,170 @@          Left parseError -> throwIO (ConfigurationParseFailed parseError)          Right rawcfg -> return rawcfg -     case runConfigParser (parseConfiguration mbPath def rawcfg) of-       Left loadError -> throwIO (ConfigurationMalformed (Text.unpack loadError))-       Right cfg -> return cfg+     case loadValue configurationSpec rawcfg of+       Left es -> throwIO+                $ ConfigurationMalformed+                $ Text.unpack+                $ Text.unlines $ map explainLoadError es+       Right cfg -> return (cfg mbPath def)  -parseConfiguration ::-  Maybe FilePath {- ^ optionally specified path to config -} ->-  ServerSettings {- ^ prepopulated default server settings -} ->-  Value ->-  ConfigParser Configuration-parseConfiguration _configConfigPath def = parseSections $+explainLoadError :: LoadError -> Text+explainLoadError (LoadError path problem) =+  Text.intercalate "." path <> ": " <>+  case problem of+    UnusedSections xs -> "Unknown sections: " <> Text.intercalate ", " xs+    MissingSection s  -> "Missing required section: " <> s+    SpecMismatch   s  -> "Expected " <> s -  do _configDefaults <- fromMaybe def-                    <$> sectionOptWith (parseServerSettings def) "defaults" -     _configServers  <- fromMaybe HashMap.empty-                    <$> sectionOptWith (parseServers _configDefaults) "servers"+configurationSpec :: ValueSpecs (Maybe FilePath -> ServerSettings -> Configuration)+configurationSpec = sectionsSpec "" $ +  do ssDefUpdate <- fromMaybe id <$> optSection' "defaults" "" serverSpec+     ssUpdates   <- fromMaybe [] <$> optSection' "servers" "" (listSpec serverSpec)+      _configPalette <- fromMaybe defaultPalette-                    <$> sectionOptWith parsePalette "palette"+                    <$> optSection' "palette" "" paletteSpec       _configWindowNames <- fromMaybe defaultWindowNames-                    <$> sectionOpt "window-names"+                    <$> optSection "window-names" ""       _configMacros <- fromMaybe mempty-                    <$> sectionOptWith parseMacroMap "macros"+                    <$> optSection' "macros" "" macroMapSpec -     _configExtensions <- fromMaybe []-                    <$> sectionOptWith (parseList parseString) "extensions"+     _configExtensions <- fromMaybe [] <$> optSection' "extensions" "" (listSpec stringSpec) -     _configUrlOpener <- sectionOptWith parseString "url-opener"+     _configUrlOpener <- optSection' "url-opener" "" stringSpec -     _configExtraHighlights <- maybe HashSet.empty HashSet.fromList-                    <$> sectionOptWith (parseList parseIdentifier) "extra-highlights"+     _configExtraHighlights <- maybe HashSet.empty (HashSet.fromList . map mkId)+                    <$> optSection "extra-highlights" "" -     _configNickPadding <- sectionOpt "nick-padding"+     _configNickPadding <- optSection' "nick-padding" "" nonnegativeSpec -     _configIndentWrapped <- sectionOpt "indent-wrapped-lines"+     _configIndentWrapped <- optSection' "indent-wrapped-lines" "" nonnegativeSpec -     _configIgnores <- maybe HashSet.empty HashSet.fromList-                    <$> sectionOptWith (parseList parseIdentifier) "ignores"+     _configIgnores <- maybe HashSet.empty (HashSet.fromList . map mkId)+                    <$> optSection "ignores" "" -     _configActivityBar <- fromMaybe False <$> sectionOpt  "activity-bar"+     _configActivityBar <- fromMaybe False+                    <$> optSection' "activity-bar" "" yesOrNoSpec -     _configBellOnMention <- fromMaybe False <$> sectionOpt  "bell-on-mention"+     _configBellOnMention <- fromMaybe False <$> optSection' "bell-on-mention" "" yesOrNoSpec -     for_ _configNickPadding (\padding ->-       when (padding < 0)-            (liftConfigParser $-               failure "nick-padding has to be a non negative number"))+     return (\_configConfigPath def ->+             let _configDefaults = ssDefUpdate def+                 _configServers  = buildServerMap _configDefaults ssUpdates+             in Configuration{..}) -     for_ _configIndentWrapped (\indent ->-       when (indent < 0)-            (liftConfigParser $-               failure "indent-wrapped-lines has to be a non negative number")) -     return Configuration{..}+nonnegativeSpec :: (Ord a, Num a) => ValueSpecs a+nonnegativeSpec = customSpec "non-negative" numSpec $ \x -> find (0 <=) [x] -parsePalette :: Value -> ConfigParser Palette-parsePalette = parseSectionsWith paletteHelper defaultPalette -paletteHelper :: Palette -> Text -> Value -> ConfigParser Palette-paletteHelper p k v =-  case k of-    "nick-colors" -> do xs <- Vector.fromList <$> parseList parseAttr v-                        when (null xs) (failure "Empty palette")-                        return $! set palNicks xs p-    _ | Just (Lens l) <- lookup k paletteMap ->-          do !attr <- parseAttr v-             return $! set l attr p-    _ -> failure "Unknown palette entry"+paletteSpec :: ValueSpecs Palette+paletteSpec = sectionsSpec "palette" $+  do updates <- catMaybes <$> sequenceA+       [ fmap (set l) <$> optSection' lbl "" attrSpec | (lbl, Lens l) <- paletteMap ]+     nickColors <- optSection' "nick-colors" "" (nonemptyList attrSpec)+     return (let pal1 = foldl' (\acc f -> f acc) defaultPalette updates+             in case nickColors of+                  Nothing -> pal1+                  Just xs -> set palNicks (Vector.fromList (NonEmpty.toList xs)) pal1) -parseServers :: ServerSettings -> Value -> ConfigParser (HashMap Text ServerSettings)-parseServers def v =-  do sss <- parseList (parseServerSettings def) v-     return (HashMap.fromList [(serverSettingName ss, ss) | ss <- sss])+nonemptyList :: ValueSpecs a -> ValueSpecs (NonEmpty a)+nonemptyList s = customSpec "non-empty" (listSpec s) NonEmpty.nonEmpty+++buildServerMap :: ServerSettings -> [ServerSettings -> ServerSettings] -> HashMap Text ServerSettings+buildServerMap def ups =+  HashMap.fromList [ (serverSettingName ss, ss) | up <- ups, let ss = up def ]   where     serverSettingName ss =       fromMaybe (views ssHostName Text.pack ss)                 (view ssName ss) -parseServerSettings :: ServerSettings -> Value -> ConfigParser ServerSettings-parseServerSettings = parseSectionsWith parseServerSetting--parseServerSetting :: ServerSettings -> Text -> Value -> ConfigParser ServerSettings-parseServerSetting ss k v =-  case k of-    "nick"                -> setFieldWith   ssNicks parseNicks-    "username"            -> setField       ssUser-    "realname"            -> setField       ssReal-    "userinfo"            -> setField       ssUserInfo-    "password"            -> setFieldMb     ssPassword-    "sasl-username"       -> setFieldMb     ssSaslUsername-    "sasl-password"       -> setFieldMb     ssSaslPassword-    "sasl-ecdsa-key"      -> setFieldWithMb ssSaslEcdsaFile parseString-    "hostname"            -> setFieldWith   ssHostName      parseString-    "port"                -> setFieldWithMb ssPort          parseNum-    "tls"                 -> setFieldWith   ssTls           parseUseTls-    "tls-client-cert"     -> setFieldWithMb ssTlsClientCert parseString-    "tls-client-key"      -> setFieldWithMb ssTlsClientKey  parseString-    "tls-server-cert"     -> setFieldWithMb ssTlsServerCert parseString-    "tls-ciphers"         -> setFieldWith   ssTlsCiphers    parseString-    "connect-cmds"        -> setFieldWith   ssConnectCmds   (parseList parseMacroCommand)-    "socks-host"          -> setFieldWithMb ssSocksHost     parseString-    "socks-port"          -> setFieldWith   ssSocksPort     parseNum-    "chanserv-channels"   -> setFieldWith   ssChanservChannels (parseList parseIdentifier)-    "flood-penalty"       -> setField       ssFloodPenalty-    "flood-threshold"     -> setField       ssFloodThreshold-    "message-hooks"       -> setField       ssMessageHooks-    "name"                -> setFieldMb     ssName-    "reconnect-attempts"  -> setField       ssReconnectAttempts-    "autoconnect"         -> setField       ssAutoconnect-    "nick-completion"     -> setFieldWith   ssNickCompletion parseNickCompletion-    "log-dir"             -> setFieldWithMb ssLogDir parseString-    _                     -> failure "Unknown setting"+serverSpec :: ValueSpecs (ServerSettings -> ServerSettings)+serverSpec = sectionsSpec "server-settings" $+  do updates <- catMaybes <$> sequenceA settings+     return (foldr (.) id updates)   where-    setField   l = setFieldWith   l parseConfig-    setFieldMb l = setFieldWithMb l parseConfig+    req l s = set l <$> s -    setFieldWith l p =-      do x <- p v-         return $! set l x ss+    opt l s = set l . Just <$> s+          <!> set l Nothing <$ atomSpec "clear" -    setFieldWithMb l p =-      do x <- case v of-                Atom "clear" -> return Nothing-                _            -> Just <$> p v-         return $! set l x ss+    settings =+      [ optSection' "name" "The name used to identify this server in the client"+      $ opt ssName valuesSpec+      , optSection' "hostname" "Hostname of server"+      $ req ssHostName stringSpec+      , optSection' "port" "Port number of server. Default 6667 without TLS or 6697 with TLS"+      $ opt ssPort numSpec+      , optSection' "nick" "Nicknames to connect with in order"+      $ req ssNicks nicksSpec+      , optSection' "password" "Server password"+      $ opt ssPassword valuesSpec+      , optSection' "username" "Second component of _!_@_ usermask"+      $ req ssUser valuesSpec+      , optSection' "realname" "\"GECOS\" name sent to server visible in /whois"+      $ req ssReal valuesSpec+      , optSection' "userinfo" "CTCP userinfo (currently unused)"+      $ req ssUserInfo valuesSpec+      , optSection' "sasl-username" "Username for SASL authentication to NickServ"+      $ opt ssSaslUsername valuesSpec+      , optSection' "sasl-password" "Password for SASL authentication to NickServ"+      $ opt ssSaslPassword valuesSpec+      , optSection' "sasl-ecdsa-key" "Path to ECDSA key for non-password SASL authentication"+      $ opt ssSaslEcdsaFile stringSpec+      , optSection' "tls" "Set to `yes` to enable secure connect. Set to `yes-insecure` to disable certificate checking."+      $ req ssTls useTlsSpec+      , optSection' "tls-client-cert" "Path to TLS client certificate"+      $ opt ssTlsClientCert stringSpec+      , optSection' "tls-client-key" "Path to TLS client key"+      $ opt ssTlsClientKey stringSpec+      , optSection' "tls-server-cert" "Path to CA certificate bundle"+      $ opt ssTlsServerCert stringSpec+      , optSection' "tls-ciphers" "OpenSSL cipher specification. Default to \"HIGH\""+      $ req ssTlsCiphers stringSpec+      , optSection' "socks-host" "Hostname of SOCKS5 proxy server"+      $ opt ssSocksHost stringSpec+      , optSection' "socks-port" "Port number of SOCKS5 proxy server"+      $ req ssSocksPort numSpec+      , optSection' "connect-cmds" "Command to be run upon successful connection to server"+      $ req ssConnectCmds $ listSpec macroCommandSpec+      , optSection' "chanserv-channels" "Channels with ChanServ permissions available"+      $ req ssChanservChannels  $ listSpec identifierSpec+      , optSection' "flood-penalty" "RFC 1459 rate limiting, seconds of penalty per message (default 2)"+      $ req ssFloodPenalty valuesSpec+      , optSection' "flood-threshold" "RFC 1459 rate limiting, seconds of allowed penalty accumulation (default 10)"+      $ req ssFloodThreshold valuesSpec+      , optSection' "message-hooks" "Special message hooks to enable: \"buffextras\" available"+      $ req ssMessageHooks valuesSpec+      , optSection' "reconnect-attempts" "Number of reconnection attempts on lost connection"+      $ req ssReconnectAttempts valuesSpec+      , optSection' "autoconnect" "Set to `yes` to automatically connect at client startup"+      $ req ssAutoconnect yesOrNoSpec+      , optSection' "nick-completion" "Behavior for nickname completion with TAB"+      $ req ssNickCompletion nickCompletionSpec+      , optSection' "log-dir" "Path to log file directory for this server"+      $ opt ssLogDir stringSpec+      ] -parseNicks :: Value -> ConfigParser (NonEmpty Text)-parseNicks (Text nick) = return (nick NonEmpty.:| [])-parseNicks (List xs) =-  do xs' <- parseList parseConfig (List xs)-     case xs' of-       [] -> failure "empty list"-       y:ys -> return (y NonEmpty.:| ys)-parseNicks _ = failure "expected text or list of text" -parseUseTls :: Value -> ConfigParser UseTls-parseUseTls (Atom "yes")          = return UseTls-parseUseTls (Atom "yes-insecure") = return UseInsecureTls-parseUseTls (Atom "no")           = return UseInsecure-parseUseTls _                     = failure "expected yes, yes-insecure, or no"+nicksSpec :: ValueSpecs (NonEmpty Text)+nicksSpec = pure <$> valuesSpec+        <!> nonemptyList valuesSpec -parseNum :: Num a => Value -> ConfigParser a-parseNum v = fromInteger <$> parseConfig v -parseIdentifier :: Value -> ConfigParser Identifier-parseIdentifier v = mkId <$> parseConfig v+useTlsSpec :: ValueSpecs UseTls+useTlsSpec =+      UseTls         <$ atomSpec "yes"+  <!> UseInsecureTls <$ atomSpec "yes-insecure"+  <!> UseInsecure    <$ atomSpec "no" -parseString :: Value -> ConfigParser String-parseString v = Text.unpack <$> parseConfig v+identifierSpec :: ValueSpecs Identifier+identifierSpec = mkId <$> valuesSpec  -- | Resolve relative paths starting at the home directory rather than -- the current directory of the client.@@ -340,34 +358,24 @@   | otherwise = do home <- getHomeDirectory                    return (home </> path) -parseMacroMap :: Value -> ConfigParser (Recognizer Macro)-parseMacroMap v = fromCommands <$> parseList parseMacro v+macroMapSpec :: ValueSpecs (Recognizer Macro)+macroMapSpec = fromCommands <$> listSpec macroValueSpecs -parseMacro :: Value -> ConfigParser (Text, Macro)-parseMacro = parseSections $-  do name     <- sectionReq "name"+macroValueSpecs :: ValueSpecs (Text, Macro)+macroValueSpecs = sectionsSpec "macro" $+  do name     <- reqSection "name" ""      spec     <- fromMaybe noMacroArguments-             <$> sectionOptWith parseMacroArguments "arguments"-     commands <- sectionReqWith (parseList parseMacroCommand) "commands"+             <$> optSection' "arguments" "" macroArgumentsSpec+     commands <- reqSection' "commands" "" (listSpec macroCommandSpec)      return (name, Macro spec commands) -parseMacroArguments :: Value -> ConfigParser MacroSpec-parseMacroArguments v =-  do txt <- parseConfig v-     case parseMacroSpecs txt of-       Nothing -> failure "bad macro argument specs"-       Just ex -> return ex+macroArgumentsSpec :: ValueSpecs MacroSpec+macroArgumentsSpec = customSpec "macro arguments" valuesSpec parseMacroSpecs -parseMacroCommand :: Value -> ConfigParser [ExpansionChunk]-parseMacroCommand v =-  do txt <- parseConfig v-     case parseExpansion txt of-       Nothing -> failure "bad macro line"-       Just ex -> return ex+macroCommandSpec :: ValueSpecs [ExpansionChunk]+macroCommandSpec = customSpec "macro command" valuesSpec parseExpansion -parseNickCompletion :: Value -> ConfigParser WordCompletionMode-parseNickCompletion v =-  case v of-    Atom "default" -> return defaultNickWordCompleteMode-    Atom "slack"   -> return slackNickWordCompleteMode-    _              -> failure "expected default or slack"+nickCompletionSpec :: ValueSpecs WordCompletionMode+nickCompletionSpec =+      defaultNickWordCompleteMode <$ atomSpec "default"+  <!> slackNickWordCompleteMode   <$ atomSpec "slack"
src/Client/Configuration/Colors.hs view
@@ -1,4 +1,5 @@ {-# Language OverloadedStrings #-}+{-# Language ApplicativeDo #-}  {-| Module      : Client.Configuration@@ -11,56 +12,50 @@ -}  module Client.Configuration.Colors-  ( parseColor-  , parseAttr+  ( colorSpec+  , attrSpec   ) where -import           Config-import           Config.FromConfig+import           Config.Schema+import           Control.Applicative import           Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap-import           Data.Foldable-import           Data.Ratio+import           Data.Functor.Alt ((<!>)) import           Data.Text (Text) import           Graphics.Vty.Attributes  -- | Parse a text attribute. This value should be a sections with the @fg@ and/or -- @bg@ attributes. Otherwise it should be a color entry that will be used -- for the foreground color. An empty sections value will result in 'defAttr'-parseAttr :: Value -> ConfigParser Attr-parseAttr (Sections xs) = parseSectionsWith parseAttrEntry defAttr (Sections xs)-parseAttr v             = withForeColor defAttr <$> parseColor v+attrSpec :: ValueSpecs Attr+attrSpec = namedSpec "attr" $+           withForeColor defAttr <$> colorSpec+       <!> fullAttrSpec -parseAttrEntry :: Attr -> Text -> Value -> ConfigParser Attr-parseAttrEntry acc k v =-    case k of-        "fg" -> parseColor' withForeColor-        "bg" -> parseColor' withBackColor-        "style" -> parseStyle'-        _    -> failure "Unknown attribute entry"+fullAttrSpec :: ValueSpecs Attr+fullAttrSpec = sectionsSpec "full-attr" $+  do mbFg <- optSection' "fg"    "Foreground color" colorSpec+     mbBg <- optSection' "bg"    "Background color" colorSpec+     mbSt <- optSection' "style" "Terminal font style" stylesSpec+     return ( aux withForeColor mbFg+            $ aux withBackColor mbBg+            $ aux (foldl withStyle) mbSt+            $ defAttr)   where-    parseStyle' =-      do xs <- parseStyles v-         return $! foldl' withStyle acc xs+    aux f xs z = foldl f z xs -    parseColor' f =-      do c <- parseColor v-         return $! f acc c -parseStyles :: Value -> ConfigParser [Style]-parseStyles (List xs) = parseList parseStyle (List xs)-parseStyles v         = pure <$> parseStyle v+stylesSpec :: ValueSpecs [Style]+stylesSpec = oneOrList styleSpec -parseStyle :: Value -> ConfigParser Style-parseStyle v =-  case v of-    Atom "blink"         -> pure blink -- You're the boss...-    Atom "bold"          -> pure bold-    Atom "dim"           -> pure dim-    Atom "reverse-video" -> pure reverseVideo-    Atom "standout"      -> pure standout-    Atom "underline"     -> pure underline-    _ -> failure "expected blink, bold, dim, reverse-video, standout, underline"+styleSpec :: ValueSpecs Style+styleSpec = namedSpec "style" $+      blink        <$ atomSpec "blink"+  <!> bold         <$ atomSpec "bold"+  <!> dim          <$ atomSpec "dim"+  <!> reverseVideo <$ atomSpec "reverse-video"+  <!> standout     <$ atomSpec "standout"+  <!> underline    <$ atomSpec "underline"   -- | Parse a color. Support formats are:@@ -68,46 +63,29 @@ -- * Number between 0-255 -- * Name of color -- * RGB values of color as a list-parseColor :: Value -> ConfigParser Color-parseColor v =-  case v of-    _ | Just i <- parseInteger v -> parseColorNumber i--    Atom a | Just c <- HashMap.lookup (atomName a) namedColors -> return c--    List [r,g,b]-      | Just r' <- parseInteger r-      , Just g' <- parseInteger g-      , Just b' <- parseInteger b ->-         parseRgb r' g' b'+colorSpec :: ValueSpecs Color+colorSpec = namedSpec "color" (colorNumberSpec <!> colorNameSpec <!> rgbSpec) -    _ -> failure "Expected a color number, name, or RBG list"+colorNameSpec :: ValueSpecs Color+colorNameSpec = customSpec "color name" anyAtomSpec (`HashMap.lookup` namedColors)  -- | Match integers between 0 and 255 as Terminal colors.-parseColorNumber :: Integer -> ConfigParser Color-parseColorNumber i-  | i < 0 = failure "Negative color not supported"-  | i < 16 = return (ISOColor (fromInteger i))-  | i < 256 = return (Color240 (fromInteger (i - 16)))-  | otherwise = failure "Color value too high"---- | Accepts any integer literal or floating literal which can--- be losslessly converted to an integer.-parseInteger :: Value -> Maybe Integer-parseInteger v =-  case v of-    Number _ i -> Just i-    Floating c e-      | denominator r == 1 -> Just (numerator r)-      where r = fromInteger c * 10^^e-    _ -> Nothing+colorNumberSpec :: ValueSpecs Color+colorNumberSpec = customSpec "terminal color" valuesSpec $ \i ->+  if      i <   0 then Nothing+  else if i <  16 then Just (ISOColor (fromInteger i))+  else if i < 256 then Just (Color240 (fromInteger (i - 16)))+  else Nothing -parseRgb :: Integer -> Integer -> Integer -> ConfigParser Color-parseRgb r g b-  | valid r, valid g, valid b = return (rgbColor r g b)-  | otherwise = failure "RGB values must be in range 0-255"+-- | Configuration section that matches 3 integers in the range 0-255+-- representing red, green, and blue values.+rgbSpec :: ValueSpecs Color+rgbSpec = customSpec "RGB" valuesSpec $ \rgb ->+  case rgb of+    [r,g,b] | valid r, valid g, valid b -> Just (rgbColor r g b)+    _                                   -> Nothing   where-    valid x = 0 <= x && x < 256+    valid x = 0 <= x && x < (256 :: Integer)  namedColors :: HashMap Text Color namedColors = HashMap.fromList
src/Client/Image/Message.hs view
@@ -396,7 +396,7 @@     Just n  -> intercalate " "              $ map (\(u,i) -> show i ++ [u])              $ dropWhile (\x -> snd x == 0)-             $ zip "dhms" [d,h,m,s]+             $ zip "dhms" [d,h,m,s :: Int]       where         (n1,s) = quotRem n  60         (n2,m) = quotRem n1 60
src/Client/Network/Async.hs view
@@ -43,7 +43,6 @@ import           Control.Lens import           Control.Monad import           Data.ByteString (ByteString)-import qualified Data.ByteString as B import           Data.Foldable import           Data.Time import           Irc.RateLimit
src/Client/Network/Connect.hs view
@@ -23,8 +23,6 @@ import           Control.Applicative import           Control.Exception  (bracket) import           Control.Lens-import           Control.Monad-import           Data.Monoid        ((<>)) import           Network.Socket     (PortNumber) import           Hookup 
src/Client/Options.hs view
@@ -32,11 +32,13 @@   , getOptions   ) where +import           Config.Schema.Docs import           Control.Lens import           Data.Foldable import           Data.List import           Data.Text (Text) import qualified Data.Text as Text+import qualified Data.Text.IO as Text import           Data.Version import           Development.GitRev (gitHash, gitDirty) import           System.Console.GetOpt@@ -47,6 +49,8 @@ import           Paths_glirc (version) import           Build_glirc (deps) +import           Client.Configuration+ -- | Command-line options data Options = Options   { _optConfigFile      :: Maybe FilePath -- ^ configuration file path@@ -55,6 +59,7 @@   , _optShowHelp        :: Bool           -- ^ show help message   , _optShowVersion     :: Bool           -- ^ show version message   , _optShowFullVersion :: Bool           -- ^ show version of ALL transitive dependencies+  , _optShowConfigFormat:: Bool           -- ^ show configuration file format   }  makeLenses ''Options@@ -68,6 +73,7 @@   , _optShowVersion     = False   , _optShowFullVersion = False   , _optNoConnect       = False+  , _optShowConfigFormat= False   }  -- | Option descriptions@@ -79,6 +85,8 @@     "Disable autoconnecting"   , Option "h" ["help"]    (NoArg (set optShowHelp True))     "Show help"+  , Option "" ["config-format"] (NoArg (set optShowConfigFormat True))+    "Show configuration file format"   , Option "v" ["version"] (NoArg (set optShowVersion True))     "Show version"   , Option "" ["full-version"] (NoArg (set optShowFullVersion True))@@ -103,11 +111,12 @@               traverse_ (hPutStr stderr) (map bullet errors)               hPutStrLn stderr tryHelpTxt -     if | view optShowHelp    opts -> putStr helpTxt    >> exitSuccess+     if | view optShowHelp    opts -> putStr helpTxt >> exitSuccess         | view optShowFullVersion opts -> putStr fullVersionTxt >> exitSuccess         | view optShowVersion opts -> putStr versionTxt >> exitSuccess+        | view optShowConfigFormat opts -> Text.putStr (generateDocs configurationSpec) >> exitSuccess         | null errors              -> return opts-        | otherwise                -> reportErrors      >> exitFailure+        | otherwise                -> reportErrors >> exitFailure  helpTxt :: String helpTxt = usageInfo "glirc2 [FLAGS] INITIAL_NETWORKS..." options
− src/Config/FromConfig.hs
@@ -1,208 +0,0 @@-{-# Language GeneralizedNewtypeDeriving #-}-{-# Language OverloadedStrings #-}--{-|-Module      : Config.FromConfig-Description : Parser for unstructure configuration file format-Copyright   : (c) Eric Mertens, 2016-License     : ISC-Maintainer  : emertens@gmail.com--This module provides tools for producing structured configuration-information out of configuration values.---}-module Config.FromConfig-  ( -- * Configuration parsing-    ConfigParser-  , decodeConfig-  , runConfigParser-  , failure-  , extendLoc-  , FromConfig(parseConfig)--  -- * Parser wrappers-  , parseList--  -- * Section parsing-  , SectionParser-  , parseSections-  , sectionReq-  , sectionReqWith-  , sectionOpt-  , sectionOptWith-  , liftConfigParser-  , parseSectionsWith-  ) where--import           Config-import           Control.Lens hiding (List)-import           Control.Monad-import           Control.Monad.Trans.Class-import           Control.Monad.Trans.Reader-import           Control.Monad.Trans.State-import           Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as HashMap-import qualified Data.HashSet as HashSet-import           Data.Monoid-import           Data.Ratio-import           Data.Text (Text)-import qualified Data.Text as Text---- | Configuration parser tracking current location and for propagating--- error information.-newtype ConfigParser a = ConfigParser (ReaderT [Text] (Either Text) a)-  deriving (Functor, Applicative, Monad)---- | Run a top-level parser to either get the parsed value or an error message.-runConfigParser :: ConfigParser a -> Either Text a-runConfigParser (ConfigParser p) = runReaderT p []---- | A parser that always fails with the given error message.-failure :: Text {- ^ error message -} -> ConfigParser a-failure msg = ConfigParser $-  do loc <- ask-     let msg' = Text.concat [Text.intercalate "." (reverse loc), ": ", msg]-     lift (Left msg')---- | Embed a parser into an extended location. This is used when--- parsing inside a section.-extendLoc :: Text -> ConfigParser a -> ConfigParser a-extendLoc loc (ConfigParser p) = ConfigParser (local (loc:) p)------------------------------------------------------------------------------ | Parse a 'Value' according to the method in the 'FromConfig'-decodeConfig :: FromConfig a => Value -> Either Text a-decodeConfig = runConfigParser . parseConfig---- | Class for types that have a well-known way to parse them.-class FromConfig a where-  -- | Parse a value-  parseConfig :: Value -> ConfigParser a---- | Matches 'Text' values.-instance FromConfig Text where-  parseConfig (Text x)          = return x-  parseConfig _                 = failure "expected text"---- | Matches @yes@ as 'True' and @no@ as 'False.-instance FromConfig Bool where-  parseConfig (Atom "yes")      = return True-  parseConfig (Atom "no")       = return False-  parseConfig _                 = failure "expected yes or no"---- | Matches 'Number' values ignoring the base-instance FromConfig Integer where-  parseConfig (Number _ n)      = return n-  parseConfig (Floating c e)-    | denominator n == 1 = return $! numerator n-    where-      n = floatingToRatio c e-  parseConfig _                 = failure "expected integral number"--instance FromConfig Int where-  parseConfig v =-    do i <- parseConfig v-       let small = minBound :: Int-           large = maxBound :: Int-       when (i < toInteger small || toInteger large < i)-          (failure "int out of range")-       return (fromInteger i)---- | Matches 'Number' values ignoring the base-instance Integral a => FromConfig (Ratio a) where-  parseConfig (Number _ n)   = return $! fromIntegral n-  parseConfig (Floating c e) = return $! floatingToRatio c e-  parseConfig _              = failure "expected fractional number"---- | Matches 'Atom' values-instance FromConfig Atom where-  parseConfig (Atom a)          = return a-  parseConfig _                 = failure "expected atom"---- | Matches 'List' values, extends the error location with a zero-based--- index-instance FromConfig a => FromConfig [a] where-  parseConfig = parseList parseConfig------------------------------------------------------------------------------ | Parser for consuming key-value pairs of sections.-newtype SectionParser a =-  SectionParser (StateT (HashMap Text Value) ConfigParser a)-  deriving (Functor, Applicative, Monad)---- | Lift a 'ConfigParser' into a 'SectionParser' leaving the current--- section information unmodified.-liftConfigParser :: ConfigParser a -> SectionParser a-liftConfigParser = SectionParser . lift---- | Run a 'SectionParser' given particular 'Value'. This will only--- succeed when the value is a 'Sections' and the section parser consumes all--- of the sections from that value.-parseSections :: SectionParser a -> Value -> ConfigParser a-parseSections (SectionParser p) (Sections xs) =-  do hm <- toHashMap xs-     (res, xs') <- runStateT p hm-     let unused = HashMap.keys xs'-     unless (null unused)-       (failure ("unknown keys: " <> Text.intercalate ", " unused))-     return res-parseSections _ _ = failure "expected sections"---- |--- @--- sectionOpt = sectionOptWith parseConfig--- @-sectionOpt :: FromConfig a => Text -> SectionParser (Maybe a)-sectionOpt = sectionOptWith parseConfig---- | Parses the value stored at the given section with the given parser.--- Nothing is returned if the section is missing.--- Just is returned if the parse succeeds--- Error is raised if the section is present but the parse fails-sectionOptWith :: (Value -> ConfigParser a) -> Text -> SectionParser (Maybe a)-sectionOptWith p key = SectionParser $-  do mb <- at key <<.= Nothing-     lift (traverse (extendLoc key . p) mb)---- | Parse the value at the given section or fail.-sectionReq :: FromConfig a => Text -> SectionParser a-sectionReq = sectionReqWith parseConfig---- | Parse the value at the given section or fail.-sectionReqWith :: (Value -> ConfigParser a) -> Text -> SectionParser a-sectionReqWith p key =-  do mb <- sectionOptWith p key-     liftConfigParser $ case mb of-                          Nothing -> failure ("section required: " <> key)-                          Just x  -> return x--toHashMap :: [Section] -> ConfigParser (HashMap Text Value)-toHashMap xs =-  case duplicateCheck xs of-    Just key -> failure ("duplicate section: " <> key)-    Nothing  -> return $! HashMap.fromList [ (k,v) | Section k v <- xs ]--duplicateCheck :: [Section] -> Maybe Text-duplicateCheck = go HashSet.empty-  where-    go _ [] = Nothing-    go seen (Section x _:xs)-      | HashSet.member x seen = Just x-      | otherwise             = go (HashSet.insert x seen) xs--floatingToRatio :: Integral a => Integer -> Integer -> Ratio a-floatingToRatio c e = fromIntegral c * 10 ^^ e--parseSectionsWith :: (a -> Text -> Value -> ConfigParser a) -> a -> Value -> ConfigParser a-parseSectionsWith p start s =-  case s of-    Sections xs -> foldM (\x (Section k v) -> extendLoc k (p x k v)) start xs-    _ -> failure "Expected sections"--parseList :: (Value -> ConfigParser a) -> Value -> ConfigParser [a]-parseList p (List xs) = ifor xs $ \i x ->-                          extendLoc (Text.pack (show (i+1))) (p x)-parseList _ _         = failure "expected list"