summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2022-10-30 01:14:02 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2022-10-30 01:14:02 +0200
commitcc5327fa854fd0274697d39b8be24475c7387e61 (patch)
treea00d7bbf4bb302e8f04779061b8a0a3b44326a7b
parentMinor bugfixes. (diff)
downloadvimwiki-scripts-cc5327fa854fd0274697d39b8be24475c7387e61.tar.gz
vimwiki-scripts-cc5327fa854fd0274697d39b8be24475c7387e61.tar.xz
Replace ini parser.
ini-config had the problem where I needed to do a low-level hack for dynamic keys. Plain ini just works.
-rw-r--r--hs/Config.hs75
-rw-r--r--hs/Data/Ini/Config.hs529
-rw-r--r--hs/Data/Ini/Config/KeyValue.hs15
-rw-r--r--hs/Data/Ini/Config/URIDict.hs27
-rw-r--r--hs/Html.hs5
-rw-r--r--hs/vimwiki.cabal16
6 files changed, 58 insertions, 609 deletions
diff --git a/hs/Config.hs b/hs/Config.hs
index 69dedf6..a2ceaeb 100644
--- a/hs/Config.hs
+++ b/hs/Config.hs
@@ -1,17 +1,20 @@
{-# LANGUAGE TemplateHaskell
, OverloadedStrings
, FlexibleInstances
+ , ImportQualifiedPost
#-}
module Config where
import Control.Lens
-import Data.Ini.Config
import Data.Map (Map)
-import Network.URI (URI)
+import Data.Map qualified as M
+import Network.URI (URI, parseURI)
import Options.Applicative
-import Data.Ini.Config.URIDict
+import Data.Ini
+import Data.Maybe (isJust, fromMaybe, fromJust)
import Data.String
+import Data.Text (Text, unpack)
data Args = Args
{ indir :: Maybe String
@@ -30,9 +33,9 @@ argp = Args
<*> strOption (long "conf" <> short 'c' <> help "Alternative Configuration" <> metavar "CONF" <> value Nothing)
<*> strOption (long "webp" <> help "Prefix for web" <> metavar "PREFIX" <> value Nothing)
-opts = info (argp <**> helper)
- (fullDesc
- <> progDesc "Build Vimwiki"
+opts = info (argp <**> helper)
+ (fullDesc
+ <> progDesc "Build Vimwiki"
<> header "build - build vimwiki")
@@ -61,25 +64,47 @@ makeLenses ''Configuration
makeLenses ''DataConfig
makeLenses ''OutputConfig
-confParser :: IniParser Configuration
-confParser = do
- out <- section "Output" $ do
- mu4eURL <- fieldMbOf "mu4eURL" string
- xapianOmega <- fieldMbOf "xapianOmega" string
- webPath <- fieldDefOf "webpath" string ""
- manProvider <- fieldMbOf "manProvider" string
- return OutputConfig { _mu4eURL = mu4eURL
- , _xapianOmega = xapianOmega
- , _manProvider = manProvider
- , _webPath = webPath }
- data' <- sectionDef "Data" (_data' defaultConf) $ do
- inputDir <- fieldOf "inputDir" string
- outputDir <- fieldOf "outputDir" string
- return DataConfig { _inputDir = inputDir, _outputDir = outputDir }
- wikis <- section "Wikis" uriDictParser
- return $ Configuration out data' wikis
-
-runParser = flip parseIniFile $ confParser
+--
+-- either to maybe
+etm :: Either a b -> Maybe b
+etm (Left _) = Nothing
+etm (Right a) = Just a
+
+fromRight (Right x) = x
+
+parseURIs :: [(Text, String)] -> Map String URI
+parseURIs lst = lst & fmap (_2 %~ parseURI)
+ & filter (isJust . snd)
+ & fmap (_2 %~ fromJust)
+ & fmap (_1 %~ unpack)
+ & M.fromList
+
+confParser :: Ini -> Configuration
+confParser ini =
+ Configuration
+ { _output = OutputConfig { _mu4eURL = d "Output" "mu4eURL"
+ , _xapianOmega = d "Output" "xapianOmega"
+ , _webPath = fromMaybe "" $ d "Output" "webpath"
+ , _manProvider = d "Output" "manProvider"
+ }
+ , _data' = if "Data" `elem` sections ini
+ then DataConfig
+ -- Thi4s will crash if key is missing
+ { _inputDir = f "Data" "inputDir"
+ , _outputDir = f "Data" "outputDir"
+ }
+ else (_data' defaultConf)
+ , _wikis = case keys "Wikis" ini of
+ Left _ -> mempty
+ -- This is safe
+ Right keys -> parseURIs $ zip keys $ f "Wikis" <$> keys
+ }
+
+ where d a b = etm $ unpack <$> lookupValue a b ini
+ f a b = unpack . fromRight $ lookupValue a b ini
+
+runParser :: Text -> Either String Configuration
+runParser t = confParser <$> parseIni t
parseCmdline = execParser opts
diff --git a/hs/Data/Ini/Config.hs b/hs/Data/Ini/Config.hs
deleted file mode 100644
index 00a3819..0000000
--- a/hs/Data/Ini/Config.hs
+++ /dev/null
@@ -1,529 +0,0 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
--- |
--- Module : Data.Ini.Config
--- Copyright : (c) Getty Ritter, 2017
--- License : BSD
--- Maintainer : Getty Ritter <config-ini@infinitenegativeutility.com>
--- Stability : experimental
---
--- The 'config-ini' library exports some simple monadic functions to
--- make parsing INI-like configuration easier. INI files have a
--- two-level structure: the top-level named chunks of configuration,
--- and the individual key-value pairs contained within those chunks.
--- For example, the following INI file has two sections, @NETWORK@
--- and @LOCAL@, and each contains its own key-value pairs. Comments,
--- which begin with @#@ or @;@, are ignored:
---
--- > [NETWORK]
--- > host = example.com
--- > port = 7878
--- >
--- > # here is a comment
--- > [LOCAL]
--- > user = terry
---
--- The combinators provided here are designed to write quick and
--- idiomatic parsers for files of this form. Sections are parsed by
--- 'IniParser' computations, like 'section' and its variations,
--- while the fields within sections are parsed by 'SectionParser'
--- computations, like 'field' and its variations. If we want to
--- parse an INI file like the one above, treating the entire
--- @LOCAL@ section as optional, we can write it like this:
---
--- > data Config = Config
--- > { cfNetwork :: NetworkConfig, cfLocal :: Maybe LocalConfig }
--- > deriving (Eq, Show)
--- >
--- > data NetworkConfig = NetworkConfig
--- > { netHost :: String, netPort :: Int }
--- > deriving (Eq, Show)
--- >
--- > data LocalConfig = LocalConfig
--- > { localUser :: Text }
--- > deriving (Eq, Show)
--- >
--- > configParser :: IniParser Config
--- > configParser = do
--- > netCf <- section "NETWORK" $ do
--- > host <- fieldOf "host" string
--- > port <- fieldOf "port" number
--- > return NetworkConfig { netHost = host, netPort = port }
--- > locCf <- sectionMb "LOCAL" $
--- > LocalConfig <$> field "user"
--- > return Config { cfNetwork = netCf, cfLocal = locCf }
---
---
--- We can run our computation with 'parseIniFile', which,
--- when run on our example file above, would produce the
--- following:
---
--- >>> parseIniFile example configParser
--- Right (Config {cfNetwork = NetworkConfig {netHost = "example.com", netPort = 7878}, cfLocal = Just (LocalConfig {localUser = "terry"})})
-module Data.Ini.Config
- ( -- * Parsing Files
- parseIniFile,
-
- -- * Parser Types
- IniParser,
- SectionParser(SectionParser),
-
- -- * Section-Level Parsing
- section,
- sections,
- sectionOf,
- sectionsOf,
- sectionMb,
- sectionDef,
-
- -- * Field-Level Parsing
- field,
- fieldOf,
- fieldMb,
- fieldMbOf,
- fieldDef,
- fieldDefOf,
- fieldFlag,
- fieldFlagDef,
-
- -- * Reader Functions
- readable,
- number,
- string,
- flag,
- listWithSeparator,
- )
-where
-
-import Control.Applicative (Alternative (..))
-import Control.Monad.Trans.Except
-import Data.Ini.Config.Raw
-import Data.Sequence (Seq)
-import qualified Data.Sequence as Seq
-import Data.String (IsString (..))
-import Data.Text (Text)
-import qualified Data.Text as T
-import Data.Typeable (Proxy (..), Typeable, typeRep)
-import GHC.Exts (IsList (..))
-import Text.Read (readMaybe)
-
-lkp :: NormalizedText -> Seq (NormalizedText, a) -> Maybe a
-lkp t = go . Seq.viewl
- where
- go ((t', x) Seq.:< rs)
- | t == t' = Just x
- | otherwise = go (Seq.viewl rs)
- go Seq.EmptyL = Nothing
-
-addLineInformation :: Int -> Text -> StParser s a -> StParser s a
-addLineInformation lineNo sec = withExceptT go
- where
- go e =
- "Line " ++ show lineNo
- ++ ", in section "
- ++ show sec
- ++ ": "
- ++ e
-
-type StParser s a = ExceptT String ((->) s) a
-
--- | An 'IniParser' value represents a computation for parsing entire
--- INI-format files.
-newtype IniParser a = IniParser (StParser RawIni a)
- deriving (Functor, Applicative, Alternative, Monad)
-
--- | A 'SectionParser' value represents a computation for parsing a single
--- section of an INI-format file.
-newtype SectionParser a = SectionParser (StParser IniSection a)
- deriving (Functor, Applicative, Alternative, Monad)
-
--- | Parse a 'Text' value as an INI file and run an 'IniParser' over it
-parseIniFile :: Text -> IniParser a -> Either String a
-parseIniFile text (IniParser mote) = do
- ini <- parseRawIni text
- runExceptT mote ini
-
--- | Find a named section in the INI file and parse it with the provided
--- section parser, failing if the section does not exist. In order to
--- support classic INI files with capitalized section names, section
--- lookup is __case-insensitive__.
---
--- >>> parseIniFile "[ONE]\nx = hello\n" $ section "ONE" (field "x")
--- Right "hello"
--- >>> parseIniFile "[ONE]\nx = hello\n" $ section "TWO" (field "x")
--- Left "No top-level section named \"TWO\""
-section :: Text -> SectionParser a -> IniParser a
-section name (SectionParser thunk) = IniParser $
- ExceptT $ \(RawIni ini) ->
- case lkp (normalize name) ini of
- Nothing -> Left ("No top-level section named " ++ show name)
- Just sec -> runExceptT thunk sec
-
--- | Find multiple named sections in the INI file and parse them all
--- with the provided section parser. In order to support classic INI
--- files with capitalized section names, section lookup is
--- __case-insensitive__.
---
--- >>> parseIniFile "[ONE]\nx = hello\n[ONE]\nx = goodbye\n" $ sections "ONE" (field "x")
--- Right (fromList ["hello","goodbye"])
--- >>> parseIniFile "[ONE]\nx = hello\n" $ sections "TWO" (field "x")
--- Right (fromList [])
-sections :: Text -> SectionParser a -> IniParser (Seq a)
-sections name (SectionParser thunk) = IniParser $
- ExceptT $ \(RawIni ini) ->
- let name' = normalize name
- in mapM
- (runExceptT thunk . snd)
- (Seq.filter (\(t, _) -> t == name') ini)
-
--- | A call to @sectionOf f@ will apply @f@ to each section name and,
--- if @f@ produces a "Just" value, pass the extracted value in order
--- to get the "SectionParser" to use for that section. This will
--- find at most one section, and will produce an error if no section
--- exists.
---
--- >>> parseIniFile "[FOO]\nx = hello\n" $ sectionOf (T.stripSuffix "OO") (\ l -> fmap ((,) l) (field "x"))
--- Right ("F","hello")
--- >>> parseIniFile "[BAR]\nx = hello\n" $ sectionOf (T.stripSuffix "OO") (\ l -> fmap ((,) l) (field "x"))
--- Left "No matching top-level section"
-sectionOf :: (Text -> Maybe b) -> (b -> SectionParser a) -> IniParser a
-sectionOf fn sectionParser = IniParser $
- ExceptT $ \(RawIni ini) ->
- let go Seq.EmptyL = Left "No matching top-level section"
- go ((t, sec) Seq.:< rs)
- | Just v <- fn (actualText t) =
- let SectionParser thunk = sectionParser v
- in runExceptT thunk sec
- | otherwise = go (Seq.viewl rs)
- in go (Seq.viewl ini)
-
--- | A call to @sectionsOf f@ will apply @f@ to each section name and,
--- if @f@ produces a @Just@ value, pass the extracted value in order
--- to get the "SectionParser" to use for that section. This will
--- return every section for which the call to @f@ produces a "Just"
--- value.
---
--- >>> parseIniFile "[FOO]\nx = hello\n[BOO]\nx = goodbye\n" $ sectionsOf (T.stripSuffix "OO") (\ l -> fmap ((,) l) (field "x"))
--- Right (fromList [("F","hello"),("B","goodbye")])
--- >>> parseIniFile "[BAR]\nx = hello\n" $ sectionsOf (T.stripSuffix "OO") (\ l -> fmap ((,) l) (field "x"))
--- Right (fromList [])
-sectionsOf :: (Text -> Maybe b) -> (b -> SectionParser a) -> IniParser (Seq a)
-sectionsOf fn sectionParser = IniParser $
- ExceptT $ \(RawIni ini) ->
- let go Seq.EmptyL = return Seq.empty
- go ((t, sec) Seq.:< rs)
- | Just v <- fn (actualText t) =
- let SectionParser thunk = sectionParser v
- in do
- x <- runExceptT thunk sec
- xs <- go (Seq.viewl rs)
- return (x Seq.<| xs)
- | otherwise = go (Seq.viewl rs)
- in go (Seq.viewl ini)
-
--- | Find a named section in the INI file and parse it with the provided
--- section parser, returning 'Nothing' if the section does not exist.
--- In order to
--- support classic INI files with capitalized section names, section
--- lookup is __case-insensitive__.
---
--- >>> parseIniFile "[ONE]\nx = hello\n" $ sectionMb "ONE" (field "x")
--- Right (Just "hello")
--- >>> parseIniFile "[ONE]\nx = hello\n" $ sectionMb "TWO" (field "x")
--- Right Nothing
-sectionMb :: Text -> SectionParser a -> IniParser (Maybe a)
-sectionMb name (SectionParser thunk) = IniParser $
- ExceptT $ \(RawIni ini) ->
- case lkp (normalize name) ini of
- Nothing -> return Nothing
- Just sec -> Just `fmap` runExceptT thunk sec
-
--- | Find a named section in the INI file and parse it with the provided
--- section parser, returning a default value if the section does not exist.
--- In order to
--- support classic INI files with capitalized section names, section
--- lookup is __case-insensitive__.
---
--- >>> parseIniFile "[ONE]\nx = hello\n" $ sectionDef "ONE" "def" (field "x")
--- Right "hello"
--- >>> parseIniFile "[ONE]\nx = hello\n" $ sectionDef "TWO" "def" (field "x")
--- Right "def"
-sectionDef :: Text -> a -> SectionParser a -> IniParser a
-sectionDef name def (SectionParser thunk) = IniParser $
- ExceptT $ \(RawIni ini) ->
- case lkp (normalize name) ini of
- Nothing -> return def
- Just sec -> runExceptT thunk sec
-
----
-
-throw :: String -> StParser s a
-throw msg = ExceptT (\_ -> Left msg)
-
-getSectionName :: StParser IniSection Text
-getSectionName = ExceptT (return . isName)
-
-rawFieldMb :: Text -> StParser IniSection (Maybe IniValue)
-rawFieldMb name = ExceptT $ \m ->
- return (lkp (normalize name) (isVals m))
-
-rawField :: Text -> StParser IniSection IniValue
-rawField name = do
- sec <- getSectionName
- valMb <- rawFieldMb name
- case valMb of
- Nothing ->
- throw
- ( "Missing field " ++ show name
- ++ " in section "
- ++ show sec
- )
- Just x -> return x
-
-getVal :: IniValue -> Text
-getVal = T.strip . vValue
-
--- | Retrieve a field, failing if it doesn't exist, and return its raw value.
---
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (field "x")
--- Right "hello"
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (field "y")
--- Left "Missing field \"y\" in section \"MAIN\""
-field :: Text -> SectionParser Text
-field name = SectionParser $ getVal `fmap` rawField name
-
--- | Retrieve a field and use the supplied parser to parse it as a value,
--- failing if the field does not exist, or if the parser fails to
--- produce a value.
---
--- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldOf "x" number)
--- Right 72
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldOf "x" number)
--- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
--- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldOf "y" number)
--- Left "Missing field \"y\" in section \"MAIN\""
-fieldOf :: Text -> (Text -> Either String a) -> SectionParser a
-fieldOf name parse = SectionParser $ do
- sec <- getSectionName
- val <- rawField name
- case parse (getVal val) of
- Left err -> addLineInformation (vLineNo val) sec (throw err)
- Right x -> return x
-
--- | Retrieve a field, returning a @Nothing@ value if it does not exist.
---
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldMb "x")
--- Right (Just "hello")
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldMb "y")
--- Right Nothing
-fieldMb :: Text -> SectionParser (Maybe Text)
-fieldMb name = SectionParser $ fmap getVal `fmap` rawFieldMb name
-
--- | Retrieve a field and parse it according to the given parser, returning
--- @Nothing@ if it does not exist. If the parser fails, then this will
--- fail.
---
--- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldMbOf "x" number)
--- Right (Just 72)
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldMbOf "x" number)
--- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
--- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldMbOf "y" number)
--- Right Nothing
-fieldMbOf :: Text -> (Text -> Either String a) -> SectionParser (Maybe a)
-fieldMbOf name parse = SectionParser $ do
- sec <- getSectionName
- mb <- rawFieldMb name
- case mb of
- Nothing -> return Nothing
- Just v -> case parse (getVal v) of
- Left err -> addLineInformation (vLineNo v) sec (throw err)
- Right x -> return (Just x)
-
--- | Retrieve a field and supply a default value for if it doesn't exist.
---
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldDef "x" "def")
--- Right "hello"
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldDef "y" "def")
--- Right "def"
-fieldDef :: Text -> Text -> SectionParser Text
-fieldDef name def = SectionParser $
- ExceptT $ \m ->
- case lkp (normalize name) (isVals m) of
- Nothing -> return def
- Just x -> return (getVal x)
-
--- | Retrieve a field, parsing it according to the given parser, and returning
--- a default value if it does not exist. If the parser fails, then this will
--- fail.
---
--- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldDefOf "x" number 99)
--- Right 72
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldDefOf "x" number 99)
--- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
--- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldDefOf "y" number 99)
--- Right 99
-fieldDefOf :: Text -> (Text -> Either String a) -> a -> SectionParser a
-fieldDefOf name parse def = SectionParser $ do
- sec <- getSectionName
- mb <- rawFieldMb name
- case mb of
- Nothing -> return def
- Just v -> case parse (getVal v) of
- Left err -> addLineInformation (vLineNo v) sec (throw err)
- Right x -> return x
-
--- | Retrieve a field and treat it as a boolean, failing if it
--- does not exist.
---
--- >>> parseIniFile "[MAIN]\nx = yes\n" $ section "MAIN" (fieldFlag "x")
--- Right True
--- >>> parseIniFile "[MAIN]\nx = yes\n" $ section "MAIN" (fieldFlag "y")
--- Left "Missing field \"y\" in section \"MAIN\""
-fieldFlag :: Text -> SectionParser Bool
-fieldFlag name = fieldOf name flag
-
--- | Retrieve a field and treat it as a boolean, subsituting
--- a default value if it doesn't exist.
---
--- >>> parseIniFile "[MAIN]\nx = yes\n" $ section "MAIN" (fieldFlagDef "x" False)
--- Right True
--- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldFlagDef "x" False)
--- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a boolean"
--- >>> parseIniFile "[MAIN]\nx = yes\n" $ section "MAIN" (fieldFlagDef "y" False)
--- Right False
-fieldFlagDef :: Text -> Bool -> SectionParser Bool
-fieldFlagDef name = fieldDefOf name flag
-
----
-
--- | Try to use the "Read" instance for a type to parse a value, failing
--- with a human-readable error message if reading fails.
---
--- >>> readable "(5, 7)" :: Either String (Int, Int)
--- Right (5,7)
--- >>> readable "hello" :: Either String (Int, Int)
--- Left "Unable to parse \"hello\" as a value of type (Int,Int)"
-readable :: forall a. (Read a, Typeable a) => Text -> Either String a
-readable t = case readMaybe str of
- Just v -> Right v
- Nothing ->
- Left
- ( "Unable to parse " ++ show str
- ++ " as a value of type "
- ++ show typ
- )
- where
- str = T.unpack t
- typ = typeRep prx
- prx :: Proxy a
- prx = Proxy
-
--- | Try to use the "Read" instance for a numeric type to parse a value,
--- failing with a human-readable error message if reading fails.
---
--- >>> number "5" :: Either String Int
--- Right 5
--- >>> number "hello" :: Either String Int
--- Left "Unable to parse \"hello\" as a value of type Int"
-number :: (Num a, Read a, Typeable a) => Text -> Either String a
-number = readable
-
--- | Convert a textual value to the appropriate string type. This will
--- never fail.
---
--- >>> string "foo" :: Either String String
--- Right "foo"
-string :: (IsString a) => Text -> Either String a
-string = return . fromString . T.unpack
-
--- | Convert a string that represents a boolean to a proper boolean. This
--- is case-insensitive, and matches the words @true@, @false@, @yes@,
--- @no@, as well as single-letter abbreviations for all of the above.
--- If the input does not match, then this will fail with a human-readable
--- error message.
---
--- >>> flag "TRUE"
--- Right True
--- >>> flag "y"
--- Right True
--- >>> flag "no"
--- Right False
--- >>> flag "F"
--- Right False
--- >>> flag "That's a secret!"
--- Left "Unable to parse \"That's a secret!\" as a boolean"
-flag :: Text -> Either String Bool
-flag s = case T.toLower s of
- "true" -> Right True
- "yes" -> Right True
- "t" -> Right True
- "y" -> Right True
- "false" -> Right False
- "no" -> Right False
- "f" -> Right False
- "n" -> Right False
- _ -> Left ("Unable to parse " ++ show s ++ " as a boolean")
-
--- | Convert a reader for a value into a reader for a list of those
--- values, separated by a chosen separator. This will split apart
--- the string on that separator, get rid of leading and trailing
--- whitespace on the individual chunks, and then attempt to parse
--- each of them according to the function provided, turning the
--- result into a list.
---
--- This is overloaded with the "IsList" typeclass, so it can be
--- used transparently to parse other list-like types.
---
--- >>> listWithSeparator "," number "2, 3, 4" :: Either String [Int]
--- Right [2,3,4]
--- >>> listWithSeparator " " number "7 8 9" :: Either String [Int]
--- Right [7,8,9]
--- >>> listWithSeparator ":" string "/bin:/usr/bin" :: Either String [FilePath]
--- Right ["/bin","/usr/bin"]
--- >>> listWithSeparator "," number "7 8 9" :: Either String [Int]
--- Left "Unable to parse \"7 8 9\" as a value of type Int"
-listWithSeparator ::
- (IsList l) =>
- Text ->
- (Text -> Either String (Item l)) ->
- Text ->
- Either String l
-listWithSeparator sep rd =
- fmap fromList . mapM (rd . T.strip) . T.splitOn sep
-
--- $setup
---
--- >>> :{
--- data NetworkConfig = NetworkConfig
--- { netHost :: String, netPort :: Int }
--- deriving (Eq, Show)
--- >>> :}
---
--- >>> :{
--- data LocalConfig = LocalConfig
--- { localUser :: Text }
--- deriving (Eq, Show)
--- >>> :}
---
--- >>> :{
--- data Config = Config
--- { cfNetwork :: NetworkConfig, cfLocal :: Maybe LocalConfig }
--- deriving (Eq, Show)
--- >>> :}
---
--- >>> :{
--- let configParser = do
--- netCf <- section "NETWORK" $ do
--- host <- fieldOf "host" string
--- port <- fieldOf "port" number
--- return NetworkConfig { netHost = host, netPort = port }
--- locCf <- sectionMb "LOCAL" $
--- LocalConfig <$> field "user"
--- return Config { cfNetwork = netCf, cfLocal = locCf }
--- >>> :}
---
--- >>> :{
--- let example = "[NETWORK]\nhost = example.com\nport = 7878\n\n# here is a comment\n[LOCAL]\nuser = terry\n"
--- >>> :}
diff --git a/hs/Data/Ini/Config/KeyValue.hs b/hs/Data/Ini/Config/KeyValue.hs
deleted file mode 100644
index d44dfc6..0000000
--- a/hs/Data/Ini/Config/KeyValue.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Data.Ini.Config.KeyValue
- ( kvParser
- ) where
-
-
-import Data.Ini.Config
-import Data.Ini.Config.Raw
-import Data.Text
-import Control.Monad.Trans.Except
-import Data.Sequence (Seq)
-
-kvParser :: SectionParser (Seq (Text,Text))
-kvParser = SectionParser $
- ExceptT $ \m ->
- return $ (\(_, v) -> (vName v, vValue v)) <$> (isVals m)
diff --git a/hs/Data/Ini/Config/URIDict.hs b/hs/Data/Ini/Config/URIDict.hs
deleted file mode 100644
index 9f35b0e..0000000
--- a/hs/Data/Ini/Config/URIDict.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-{-# LANGUAGE ImportQualifiedPost #-}
-
-module Data.Ini.Config.URIDict
- ( uriDictParser
- ) where
-
-import Control.Lens
-import Data.Foldable (toList)
-import Data.Ini.Config
-import Data.Ini.Config.KeyValue
-import Data.Map (Map)
-import Data.Map qualified as Map
-import Data.Maybe (isJust, fromJust)
-import Data.Text hiding (filter)
-import Network.URI (URI, parseURI)
-
-uriDictParser :: SectionParser (Map String URI)
--- uriDictParser = fromList . filter (isJust . snd) . fmap (_2 %~ parseURI) $ kvParser
--- uriDictParser = fmap kvParser
-uriDictParser = fmap (Map.fromList)
- $ fmap (fmap (_2 %~ fromJust))
- $ fmap (filter (isJust . snd))
- $ fmap toList
- $ fmap (fmap (_1 %~ unpack . strip))
- $ fmap (fmap (_2 %~ parseURI . unpack . strip))
- -- $ fmap (fmap (_2 .~ Just nullURI))
- $ kvParser
diff --git a/hs/Html.hs b/hs/Html.hs
index 43040e4..ff8df89 100644
--- a/hs/Html.hs
+++ b/hs/Html.hs
@@ -102,7 +102,7 @@ resolveInterwiki :: Map String URI -> String -> URI -> String
resolveInterwiki wikilinks wikiname uri =
case M.lookup wikiname wikilinks of
Just uri' -> show $ uri' & uriPathLens %~ (<> fixExtension (uriPath uri))
- Nothing -> error $ "Unknown Wiki name: " <> wikiname <> "'"
+ Nothing -> error $ "Unknown Wiki name: " <> wikiname <> ""
@@ -253,8 +253,9 @@ _ .~? Nothing = id
lens .~? (Just x) = lens .~ x
infixr 4 .~?
-stripTrailing :: Eq a => a -> [a] -> [a]
+stripTrailing :: (Show a, Eq a) => a -> [a] -> [a]
stripTrailing c s
+ | null s = s
| c == last s = init s
| otherwise = s
diff --git a/hs/vimwiki.cabal b/hs/vimwiki.cabal
index 04d37c5..c52c479 100644
--- a/hs/vimwiki.cabal
+++ b/hs/vimwiki.cabal
@@ -32,29 +32,23 @@ executable Main
Links,
Html,
Handlingar,
- Data.Ini.Config,
- Data.Ini.Config.KeyValue,
- Data.Ini.Config.URIDict,
Vimwiki.Man,
System.Home,
Util
build-depends:
+ base >= 4.8,
blaze-html >= 0.9,
+ containers >= 0.5,
data-default >= 0.7,
directory >= 1.3.6,
filepath >= 1.4.2,
+ ini >= 0.4.2,
lens >= 5.1,
network-uri >= 2.6.4,
optparse-applicative >= 0.17,
pandoc >= 2.19,
pandoc-types >= 1.22,
+ text >= 1.2.2,
unix >= 2.7.2,
- xdg-basedir >= 0.2,
- -- These are locked by config-ini, since Data.Ini.Config
- -- is a vendored (and barely changed) file
- config-ini == 0.2.5.0,
- base (>=4.8 && <5),
- containers (>=0.5 && <0.7),
- text (>=1.2.2 && <2.1),
- transformers (>=0.4.1 && <0.6)
+ xdg-basedir >= 0.2
default-language: Haskell2010