summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2022-10-29 13:11:06 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2022-10-29 13:11:06 +0200
commit3da17902b31192d307c4adbfdb1f2e9c693713bc (patch)
tree6107f28a387146b9263a8ac5658f546a9e5046ea
parentSetup basic configuration. (diff)
downloadvimwiki-scripts-3da17902b31192d307c4adbfdb1f2e9c693713bc.tar.gz
vimwiki-scripts-3da17902b31192d307c4adbfdb1f2e9c693713bc.tar.xz
Major cleanup.
-rw-r--r--hs/Config.hs52
-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/Files.hs13
-rw-r--r--hs/Handlingar.hs3
-rw-r--r--hs/Html.hs268
-rw-r--r--hs/System/Home.hs27
-rw-r--r--hs/Util.hs3
-rw-r--r--hs/Vimwiki/Man.hs42
-rw-r--r--hs/config.hs5
-rw-r--r--hs/main.hs10
-rw-r--r--hs/private.ini13
-rw-r--r--hs/public.ini8
-rw-r--r--hs/vimwiki.cabal41
15 files changed, 825 insertions, 231 deletions
diff --git a/hs/Config.hs b/hs/Config.hs
index 691f67f..69dedf6 100644
--- a/hs/Config.hs
+++ b/hs/Config.hs
@@ -1,40 +1,45 @@
{-# LANGUAGE TemplateHaskell
, OverloadedStrings
+ , FlexibleInstances
#-}
module Config where
import Control.Lens
-
-import Options.Applicative
-
import Data.Ini.Config
+import Data.Map (Map)
+import Network.URI (URI)
+import Options.Applicative
+import Data.Ini.Config.URIDict
+import Data.String
data Args = Args
- { indir :: String
- , outdir :: String
- }
+ { indir :: Maybe String
+ , outdir :: Maybe String
+ , conffile :: Maybe String
+ , webp :: Maybe String
+ } deriving (Show)
+
+instance IsString (Maybe String) where
+ fromString a = Just a
argp :: Parser Args
argp = Args
- <$> strOption (long "indir" <> short 'i' <> help "Input Directory" <> metavar "DIR")
- <*> strOption (long "outdir" <> short 'o' <> help "Output Directory" <> metavar "DIR")
+ <$> strOption (long "indir" <> short 'i' <> help "Input Directory" <> metavar "DIR" <> value Nothing)
+ <*> strOption (long "outdir" <> short 'o' <> help "Output Directory" <> metavar "DIR" <> value Nothing)
+ <*> 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"
<> header "build - build vimwiki")
-{-
-data Wiki = Wiki
- { name :: String
- , url :: String } deriving (Eq, Show)
--}
data Configuration = Configuration
{ _output :: OutputConfig
, _data' :: DataConfig
- -- , _wikis :: [Wiki]
+ , _wikis :: Map String URI
} deriving (Eq, Show)
data DataConfig = DataConfig
@@ -49,6 +54,7 @@ data OutputConfig = OutputConfig
, _xapianOmega :: Maybe String
-- Are we in a subdirectory from the web root?
, _webPath :: String
+ , _manProvider :: Maybe String
} deriving (Eq, Show)
makeLenses ''Configuration
@@ -58,18 +64,20 @@ makeLenses ''OutputConfig
confParser :: IniParser Configuration
confParser = do
out <- section "Output" $ do
- mu4eURL <- fieldMbOf "mu4eURL" string
+ mu4eURL <- fieldMbOf "mu4eURL" string
xapianOmega <- fieldMbOf "xapianOmega" string
- webPath <- fieldDefOf "webpath" 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
+ inputDir <- fieldOf "inputDir" string
outputDir <- fieldOf "outputDir" string
return DataConfig { _inputDir = inputDir, _outputDir = outputDir }
- -- wikis <- section "wikis"
- return $ Configuration out data'
+ wikis <- section "Wikis" uriDictParser
+ return $ Configuration out data' wikis
runParser = flip parseIniFile $ confParser
@@ -80,9 +88,11 @@ defaultConf = Configuration
{ _mu4eURL = Nothing
, _xapianOmega = Nothing
, _webPath = ""
+ , _manProvider = Nothing
}
, _data' = DataConfig
- { _inputDir = "/home/user/wiki"
- , _outputDir = "/tmp/wiki-output"
+ { _inputDir = ""
+ , _outputDir = ""
}
+ , _wikis = mempty
}
diff --git a/hs/Data/Ini/Config.hs b/hs/Data/Ini/Config.hs
new file mode 100644
index 0000000..00a3819
--- /dev/null
+++ b/hs/Data/Ini/Config.hs
@@ -0,0 +1,529 @@
+{-# 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
new file mode 100644
index 0000000..d44dfc6
--- /dev/null
+++ b/hs/Data/Ini/Config/KeyValue.hs
@@ -0,0 +1,15 @@
+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
new file mode 100644
index 0000000..9f35b0e
--- /dev/null
+++ b/hs/Data/Ini/Config/URIDict.hs
@@ -0,0 +1,27 @@
+{-# 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/Files.hs b/hs/Files.hs
index 1f85297..20554c1 100644
--- a/hs/Files.hs
+++ b/hs/Files.hs
@@ -2,6 +2,7 @@ module Files
( mkdirP
, fileTree
, isFiletype
+, copyFiles
) where
import System.Directory hiding (isSymbolicLink)
@@ -19,10 +20,9 @@ import System.Posix.Files
import System.FilePath
( joinPath
, takeExtension
+ , (</>)
)
-import Data.List.Extra (snoc)
-
mkdirP = createDirectoryIfMissing True
fmt :: FileStatus -> String
@@ -43,7 +43,7 @@ fileTree base = do
items <- listDirectory (joinPath base)
concat <$>
mapM (\entry -> do
- let here = snoc base entry
+ let here = base <> [entry]
let path = joinPath here
st <- getFileStatus path
let d = (st, here)
@@ -56,3 +56,10 @@ isFiletype :: String -> FileStatus -> [FilePath] -> Bool
isFiletype extension st path
= isRegularFile st && (takeExtension . last $ path) == ('.' : extension)
+copyFile' :: FilePath -> FileStatus -> [FilePath] -> IO ()
+copyFile' dest st path
+ | isDirectory st = createDirectoryIfMissing True (dest </> (joinPath $ tail path))
+ | otherwise = copyFile (joinPath path) $ dest </> (joinPath $ tail path)
+
+copyFiles :: FilePath -> [(FileStatus, [FilePath])] -> IO ()
+copyFiles dest = mapM_ (uncurry $ copyFile' dest)
diff --git a/hs/Handlingar.hs b/hs/Handlingar.hs
index 36d1a35..281223f 100644
--- a/hs/Handlingar.hs
+++ b/hs/Handlingar.hs
@@ -10,7 +10,6 @@ import Text.Pandoc
)
import Text.Pandoc.Builder
import Data.Text (Text, pack)
-import Text.Show.Pretty (pPrintList)
import System.Environment (getArgs)
import Data.Default (def)
@@ -63,7 +62,7 @@ main = do
pandoc <- runIOorExplode $ readVimwiki def text
let Pandoc _ blocks = pandoc
-- putStr . valToStr $ blocks
- pPrintList $ getHeadingData (pack heading) blocks
+ mapM_ print $ getHeadingData (pack heading) blocks
-- let htmlString = toStrict . renderHtml $ html
-- liftIO $ T.writeFile outTarget htmlString
_ -> error "Invalid command line"
diff --git a/hs/Html.hs b/hs/Html.hs
index 8a24e48..48f598a 100644
--- a/hs/Html.hs
+++ b/hs/Html.hs
@@ -7,9 +7,6 @@ module Html
( main
) where
-import Control.Lens.Operators hiding ((<.>))
-
-import System.Environment (lookupEnv)
import System.FilePath
( joinPath
, takeDirectory
@@ -20,150 +17,91 @@ import System.FilePath
, dropExtension
, takeBaseName
)
-import System.Directory (copyFile, createDirectoryIfMissing)
-
-import Text.Pandoc
- ( PandocMonad
- , PandocIO
- -- , ReaderOptions( readerStandalone
- -- )
- -- , WriterOptions( writerTemplate
- -- , writerTableOfContents
- -- , writerVariables
- -- )
- , runIOorExplode
- -- , compileDefaultTemplate
- -- , compileTemplate
- -- , getTemplate
- , writeHtml5
- , readVimwiki
- , toLang
- , setTranslations
- )
-
--- import Text.Pandoc.Class
--- ( getsCommonState
--- , CommonState(stLog)
--- )
-
-import Text.Pandoc.Shared (stringify)
+import Control.Lens.Operators hiding ((<.>))
+import Control.Lens.Setter (ASetter)
-import Text.Pandoc.Writers.Shared
- ( toTableOfContents
- , lookupMetaString
- )
+import Control.Monad.IO.Class (liftIO)
+import Data.Default (def)
+import Data.List (partition)
+import Data.Map qualified as M
+import Data.Map (Map)
+import Data.Maybe (listToMaybe, fromMaybe, fromJust)
+import Data.Set (Set)
+import Data.Set qualified as S
import Data.Text (Text, unpack, pack)
import Data.Text.IO qualified as T
import Data.Text.Lazy (toStrict)
---import Network.URI --(parseURI, uriPath, uriScheme, uriFragment, uriQuery, uriAuthority, URI, URIAuth)
+
import Network.URI hiding (query)
import Network.URI.Lens
-import Data.Maybe (listToMaybe, fromMaybe, fromJust)
-import Data.List (partition)
-
-import Control.Monad.IO.Class (liftIO)
-import Data.Default (def)
-
--- import Text.Blaze
-import Text.Blaze.Html ()
-import Text.Blaze.Html.Renderer.Text (renderHtml)
-import Text.Pandoc.Walk
- ( Walkable (..)
- , walk )
+import System.Directory (copyFile, createDirectoryIfMissing)
+import System.Environment.XDG.BaseDir (getUserConfigDir)
-import Text.Blaze.Html5 hiding (main, html, summary, header, string, section, output)
+import Text.Blaze.Html5 (Html, toHtml, (!), textValue, docTypeHtml)
import Text.Blaze.Html5 qualified as H
-import Text.Blaze.Html5.Attributes hiding (summary)
+import Text.Blaze.Html5.Attributes (href, role, rel, content, name, charset)
import Text.Blaze.Html5.Attributes qualified as A
-import Text.Pandoc.Definition
- ( Pandoc (Pandoc)
+import Text.Blaze.Html.Renderer.Text (renderHtml)
+
+import Text.Pandoc
+ ( PandocMonad
+ , PandocIO
+ , runIOorExplode
+ , writeHtml5
+ , readVimwiki
+ , toLang
+ , setTranslations
+ , Pandoc (Pandoc)
, Inline (..)
, Block (..)
, Format(Format)
)
--- import Text.Pandoc.Builder
+
+import Text.Pandoc.Shared (stringify)
+import Text.Pandoc.Walk (Walkable (..), walk)
+import Text.Pandoc.Writers.Shared (toTableOfContents, lookupMetaString)
+
import Files
import Links
import Util
import Config
-
--- import Text.DocTemplates (Context(..), ToContext(toVal))
-import Data.Map qualified as M
-import Data.Set (Set)
-import Data.Set qualified as S
-import Data.String (IsString)
+-- import System.Home
+import Vimwiki.Man
isWikiFile = isFiletype "wiki"
-- TODO
-- Colorful headers?
-- All headers should link to themselves
+-- remove all fromJust, instead omit those elements
-{-
-vimwikiToHTML :: Text -> IO Text
--- vimwikiToHTML txt = runIOorExplode $ do
--- pdoc <- readVimwiki def txt
--- html <- writeHtml5 def pdoc
--- return $ toStrict . renderHtml $ html
-vimwikiToHTML txt = do
- pdoc <- runIOorExplode $ readVimwiki def txt
- print pdoc
- html <- runIOorExplode $ writeHtml5 def pdoc
- return $ toStrict . renderHtml $ html
--}
-
-type UrlRewriter = [Inline] -> URI -> ([Inline], Text)
-
-mailRewriter :: String -> UrlRewriter
+
+mailRewriter :: String -> [Inline] -> URI -> ([Inline], Text)
mailRewriter mu4eURL body uri = (body, pack $ mu4eURL <> "?id=" <> uriPath uri)
--- Comparison of online man pages:
--- https://gist.github.com/rixx/6cb5fa38f694009ad0bd50c275bb61f2
-
-archMan :: (Semigroup a, IsString a) => a -> Maybe a -> Maybe a -> a
-archMan page section' language'
- = "https://man.archlinux.org/man/" <> page <> f section' <> f language'
- where f = fromMaybe "" . fmap ("." <>)
-
-mannedMan :: (Semigroup a, IsString a) => a -> Maybe a -> Maybe a -> a
-mannedMan page section' language'
- = "https://manned.org/man" <> f language' <> "/" <> page <> f section'
- where f = fromMaybe "" . fmap ("." <>)
-
-manRewriter :: UrlRewriter
-manRewriter _ uri
- = ([Str $ path <> "(" <> (fromMaybe "?" section') <> ")"]
- , mannedMan path section' language')
- where path :: Text
- path = pack $ uriPath uri
- section' = pack . tail <$> nullToMaybe (uriFragment uri)
- language' = pack . tail <$> nullToMaybe (uriQuery uri)
-
-urlRewrites :: Configuration -> M.Map String UrlRewriter
+urlRewrites :: Configuration -> Map String ([Inline] -> URI -> ([Inline], Text))
urlRewrites conf = M.fromList [ ("mail", mailRewriter $ (fromJust $ conf ^. output . mu4eURL))
- , ("man", manRewriter)
+ -- , ("man", manRewriter $ (rewriters ! (fromJust $ conf ^. output . manProvider)))
+ -- TODO choose man implementation from
+ -- configuration item
+ , ("man", manRewriter $ mannedMan)
]
--- -- TODO don't do this for inter-page links
--- | otherwise = Link attributes body (url <> ".html", title)
-
fixExtension url
| hasExtension url = url
| otherwise = url <.> "html"
--- TODO make this part of the configuration
-resolveInterwiki :: String -> URI -> String
-resolveInterwiki wikiname uri =
- case wikiname of
- "public" -> show $ (fromJust $ parseURI "http://wiki.gandalf.adrift.space/") & uriPathLens %~ (<> uriPath uri <> ".html")
- "private" -> show $ (fromJust $ parseURI "http://wiki.gandalf.adrift.space/private/") & uriPathLens %~ (<> uriPath uri <> ".html")
- _ -> error $ "Unknown Wiki name: '" <> wikiname <> "'"
+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 <> "'"
@@ -175,10 +113,10 @@ rebuildLinks conf l@(Link attributes body (url, title@"wikilink"))
in Link attributes
[ Str . pack . uriPath $ uri
, Superscript [Str $ pack wikiname]]
- (pack $ resolveInterwiki wikiname uri, title)
+ (pack $ resolveInterwiki (conf ^. wikis) wikiname uri, title)
_ -> case M.lookup (init . uriScheme $ uri) (urlRewrites conf) of
Just proc -> (proc body uri) & (\(body, url) -> Link attributes body (url, title))
- Nothing -> l -- Link attributes body (url <> ".html", title)
+ Nothing -> l
Nothing -> case break (== '#') (unpack url) of
("", '#':_) -> Link attributes body (url, title)
(page, "") -> Link attributes body (pack $ fixExtension page, title)
@@ -186,10 +124,6 @@ rebuildLinks conf l@(Link attributes body (url, title@"wikilink"))
_ -> l
rebuildLinks _ l = l
--- Link ("",[],[]) [Str "#M\246te 7"] ("#M\246te 7","wikilink")
--- Link ("",[],[]) [Str "https://styrdokument.liu.se/Regelsamling/Fil/1513619"] ("https://styrdokument.liu.se/Regelsamling/Fil/1513619","")
--- Link ("",[],[]) [Str "Beslut",Space,Str "LIU-2020-02033"] ("file:LinTek/2021/Beslut om riktlinjer och rutiner f\246r genomf\246rande av skriftliga salsskrivningar inklusive digitala tent.pdf","wikilink")
-
checkbox extra = RawInline (Format "html")
$ "<input type='checkbox' disabled='true' " <> extra <> "/>"
@@ -214,39 +148,7 @@ applyFilters conf pandoc = pandoc
& walk buildCheckboxes'
handleSourceText :: PandocMonad m => Configuration -> Text -> m Pandoc
-handleSourceText conf text = do
- pandoc <- readVimwiki def text
-
- --runIO $ print . show $ pandoc
- -- Pandoc (Meta {unMeta = fromList [ ("date",MetaInlines [Str "2000-01-01"])
- -- , ("title",MetaInlines [Str "Custom",Space,Str "Title"])]})
- -- [ Header 1 ("Test Wiki",[],[]) [Str "Test",Space,Str "Wiki"]
- -- , Header 1 ("Generated Tags",[],[]) [Str "Generated",Space,Str "Tags"]]
-
- let Pandoc meta blocks = pandoc
- -- let pandoc = Pandoc (meta & setMeta "lang" ("sv-SE"::Text)) blocks
- let pandoc = Pandoc meta blocks
-
- let pandoc' = pandoc
- & (applyFilters conf)
- -- & setTitle (str "Some form of title")
- -- & setAuthors [ str "Hugo Hörnquist" ]
- -- & setDate (str "2022-01-26")
-
- -- template <- compileDefaultTemplate "html5"
- {-
- templateData <- getTemplate "template.html5"
- template' <- compileTemplate "template.html5" templateData
- template <- case template' of
- Left err -> error err
- Right template -> return template
- -}
-
- return pandoc'
-
--- Becomes wikilink when the URI scheme doesn't match one of
--- Text.Pandoc.Shared (schemes)
--- Link (_,_,_) _ (_, "wikilink")
+handleSourceText conf text = (& applyFilters conf) <$> readVimwiki def text
breadcrumbLinks :: Configuration -> [FilePath] -> [String]
breadcrumbLinks conf parts = f <$> (accumulate $ (conf ^. output . webPath) : parts)
@@ -265,8 +167,8 @@ htmlWrap conf title parts backlinks toc main = docTypeHtml $ do
H.meta ! charset "utf-8"
H.meta ! name "generator" ! content "pandoc"
H.meta ! name "viewport" ! content "width=device-width, initial-scale=1.0, user-scalable=yes"
- H.link ! rel "stylesheet" ! (href . textValue $ (pack $ conf ^. output . webPath) <> "/style.css")
- H.script ! (A.src . textValue $ ((pack $ conf ^. output . webPath) <> "/script.js")) $ mempty
+ H.link ! rel "stylesheet" ! (href . textValue $ (pack $ conf ^. output . webPath </> "style.css"))
+ H.script ! (A.src . textValue $ (pack $ conf ^. output . webPath </> "script.js")) $ mempty
H.title $ H.string $ title <> " — Vimwiki"
H.body $ do
H.header $ do
@@ -308,7 +210,7 @@ headerContent _ = Nothing
-handlePart :: Configuration -> FilePath -> M.Map Text (Set FilePath) -> FilePath -> [FilePath] -> PandocIO ()
+handlePart :: Configuration -> FilePath -> Map Text (Set FilePath) -> FilePath -> [FilePath] -> PandocIO ()
handlePart conf outdir backlinks wiki_root parts = do
let item_path = joinPath parts
let inTarget = wiki_root </> item_path
@@ -325,8 +227,8 @@ handlePart conf outdir backlinks wiki_root parts = do
pandoc <- handleSourceText conf text
let Pandoc meta blocks = pandoc
toc <- writeHtml5 def $ Pandoc meta $ [toTableOfContents def blocks]
- -- writeHtml5 htmlAttr pandoc
html <- writeHtml5 def pandoc
+
-- Since we (explicitly) don't reset the state between invocations
-- the list of log entries continues to grow. We could check if
-- before and after, and check if it is langer
@@ -341,38 +243,62 @@ handlePart conf outdir backlinks wiki_root parts = do
let htmlString = toStrict . renderHtml . htmlWrap conf (unpack title) parts bl toc $ html
liftIO $ T.writeFile outTarget htmlString
--- TODO copy remaining files verbatim
+
+-- Like '.~', but the RHS is only possibly a value. Set the value in
+-- the structure when there is one, and do nothing when there isn't
+-- one.
+(.~?) :: ASetter s s a b -> Maybe b -> s -> s
+_ .~? Nothing = id
+lens .~? (Just x) = lens .~ x
+infixr 4 .~?
+
main = do
args <- parseCmdline
- let indir' = indir args
- let outdir' = outdir args
- (wiki_files, other_files) <- partition (uncurry isWikiFile) <$> fileTree [ indir' ]
- wp <- fromMaybe "" <$> lookupEnv "WEB_PATH"
+ confDir <- getUserConfigDir "vimwiki"
- confText <- T.readFile "config.ini"
+ confText <- T.readFile $ fromMaybe (confDir </> "config.ini") (conffile args)
conf' <- case runParser confText of
Left err -> error err
Right conf -> return conf
- -- conf' <- (read :: String -> Configuration) <$> readFile' "/home/hugo/code/vimwiki-scripts/hs/config.hs"
- -- TODO Merge defaultConfig
- let conf = (conf' & data' . inputDir .~ indir'
- & data' . outputDir .~ outdir'
- & output . webPath .~ wp
- )
+ let conf = (conf' & data' . inputDir .~? indir args
+ & data' . outputDir .~? outdir args
+ & output . webPath .~? webp args
+ )
+
+ -- indir' <- expandTilde `mapM` indir args
+ -- outdir' <- expandTilde `mapM` outdir args
+
+ -- print conf
+ if null (conf ^. data' . inputDir)
+ then error "No input directory given"
+ else return ()
+
+ if null (conf ^. data' . outputDir)
+ then error "No output directory given"
+ else return ()
+
+ print $ "input = " <> (conf ^. data' . inputDir)
+ print $ "output = " <> (conf ^. data' . outputDir)
+
+ (wikiFiles, otherFiles) <- partition (uncurry isWikiFile) <$> fileTree [ conf ^. data' . inputDir ]
+
+ -- mapM_ print otherFiles
+ -- TODO ignore hidden files
+ copyFiles (conf ^. (data' . outputDir)) otherFiles
createDirectoryIfMissing True $ conf ^. data' . outputDir
- copyFile "style2.css" (conf ^. (data' . outputDir) <> "/style.css")
- copyFile "script.js" (conf ^. (data' . outputDir) <> "/script.js")
+ copyFile "style2.css" $ (conf ^. (data' . outputDir)) <> "/style.css"
+ copyFile "script.js" $ (conf ^. (data' . outputDir)) <> "/script.js"
-- What each page links to
-- forwardLinks :: [(FilePath, [String])]
- let relative_paths = tail . snd <$> wiki_files
- forwardLinks <- runIOorExplode $ mapM (findLinks indir') relative_paths
- -- TODO backlinks should look at basename of filename, for both
- -- link and target.
+ let relative_paths = tail . snd <$> wikiFiles
+ forwardLinks <- runIOorExplode $ mapM (findLinks $ conf ^. data' . inputDir) relative_paths
+
+ -- TODO backlinks should look at basename of filename, for both link and target.
-- http://wiki.gandalf.adrift.space/Lysator/Styrelse/wi_1233359070.html
let backlinks = M.unions $ uncurry buildBacklinkSet <$> forwardLinks
@@ -384,5 +310,5 @@ main = do
Nothing -> return ()
Just l -> setTranslations l
- mapM_ (handlePart conf outdir' backlinks indir') relative_paths
- -- mapM_ (putStrLn . show) wiki_files
+ mapM_ (handlePart conf (conf^.data'.outputDir) backlinks $ (conf ^. data' . inputDir)) relative_paths
+ -- mapM_ (putStrLn . show) wikiFiles
diff --git a/hs/System/Home.hs b/hs/System/Home.hs
new file mode 100644
index 0000000..30a08f1
--- /dev/null
+++ b/hs/System/Home.hs
@@ -0,0 +1,27 @@
+module System.Home where
+
+import System.IO.Error
+import System.Posix.User
+import System.FilePath
+
+-- Return home directory for given user, or currently logged in user.
+getUserHome :: Maybe String -> IO (Either IOError String)
+getUserHome Nothing = (Just <$> getEffectiveUserName) >>= getUserHome
+getUserHome (Just name) = tryIOError $ homeDirectory <$> getUserEntryForName name
+
+-- If the first component of path is either a tilde `~', or a tilde
+-- joined with a user name `~hugo', expand that to the revelant home
+-- directory, and return the modified path.
+expandTilde :: FilePath -> IO FilePath
+expandTilde path = do
+ let (fst' : rest) = splitDirectories path
+ mfst <- case fst' of
+ "~" -> getUserHome Nothing
+ ('~':name) -> getUserHome (Just name)
+ _ -> return . Left . userError $ "Nothing"
+ return . joinPath $ case mfst of
+ Right fst -> fst : rest
+ Left _ -> rest
+
+
+
diff --git a/hs/Util.hs b/hs/Util.hs
index 77e4017..06a7e86 100644
--- a/hs/Util.hs
+++ b/hs/Util.hs
@@ -2,7 +2,6 @@ module Util
( accumulate
, intersperse
, joinBy
--- , (&)
, nullToMaybe
, firstJust
) where
@@ -22,8 +21,6 @@ intersperse y (x:xs) = x : y : intersperse y xs
joinBy :: Monoid a => a -> [a] -> a
joinBy delim lst = mconcat $ intersperse delim lst
-(&) = flip ($)
-
nullToMaybe :: (Eq a, Monoid a) => a -> Maybe a
nullToMaybe m
diff --git a/hs/Vimwiki/Man.hs b/hs/Vimwiki/Man.hs
new file mode 100644
index 0000000..dde9028
--- /dev/null
+++ b/hs/Vimwiki/Man.hs
@@ -0,0 +1,42 @@
+{-# LANGUAGE OverloadedStrings
+ , ImportQualifiedPost
+ #-}
+
+module Vimwiki.Man where
+
+import Data.Map (Map)
+import Data.Map qualified as M
+import Data.Maybe (fromMaybe)
+import Data.String (IsString)
+import Data.Text hiding (tail)
+import Network.URI
+import Text.Pandoc (Inline (Str))
+import Util (nullToMaybe)
+
+manRewriter :: (Text -> Maybe Text -> Maybe Text -> Text) -> [Inline] -> URI -> ([Inline], Text)
+manRewriter manImpl _ uri
+ = ([Str $ path <> "(" <> (fromMaybe "?" section') <> ")"]
+ , manImpl path section' language')
+ where path :: Text
+ path = pack $ uriPath uri
+ section' = pack . tail <$> nullToMaybe (uriFragment uri)
+ language' = pack . tail <$> nullToMaybe (uriQuery uri)
+
+archMan :: (Semigroup a, IsString a) => a -> Maybe a -> Maybe a -> a
+archMan page section' language'
+ = "https://man.archlinux.org/man/" <> page <> f section' <> f language'
+ where f = fromMaybe "" . fmap ("." <>)
+
+mannedMan :: (Semigroup a, IsString a) => a -> Maybe a -> Maybe a -> a
+mannedMan page section' language'
+ = "https://manned.org/man" <> f language' <> "/" <> page <> f section'
+ where f = fromMaybe "" . fmap ("." <>)
+
+
+-- Comparison of online man pages:
+-- https://gist.github.com/rixx/6cb5fa38f694009ad0bd50c275bb61f2
+rewriters :: (Semigroup a, IsString a) => Map String (a -> Maybe a -> Maybe a -> a)
+rewriters = M.fromList [ ("arch", archMan)
+ , ("manned", mannedMan)
+ ]
+
diff --git a/hs/config.hs b/hs/config.hs
deleted file mode 100644
index eee62e5..0000000
--- a/hs/config.hs
+++ /dev/null
@@ -1,5 +0,0 @@
-Configuration
-{ mu4eURL = "http://localhost:8090"
-, xapianOmega = "http://wiki.gandalf.adrift.space/search"
-}
-
diff --git a/hs/main.hs b/hs/main.hs
index 11339da..1b38cba 100644
--- a/hs/main.hs
+++ b/hs/main.hs
@@ -8,15 +8,9 @@ import qualified Html
main = Html.main
-
-- TODO
--- at top, trace to go back
--- in footer,
--- backlinks
---
--- Special url types
--- - wn.public:
--- - mail:
+-- Fix backlinks
+-- Link to git repo in footer
--
--
-- För handlingar:
diff --git a/hs/private.ini b/hs/private.ini
new file mode 100644
index 0000000..c62c2a1
--- /dev/null
+++ b/hs/private.ini
@@ -0,0 +1,13 @@
+[Output]
+mu4eURL = http://localhost:8090
+xapianOmega = http://wiki.gandalf.adrift.space/search
+manProvider = manned
+webPath = /private
+
+[Data]
+inputDir = ~/wiki/private
+outputDir = /tmp/wiki/private
+
+[Wikis]
+public = http://wiki.gandalf.adrift.space/
+private = http://wiki.gandalf.adrift.space/private/
diff --git a/hs/public.ini b/hs/public.ini
new file mode 100644
index 0000000..0b53a55
--- /dev/null
+++ b/hs/public.ini
@@ -0,0 +1,8 @@
+[Output]
+mu4eURL = http://localhost:8090
+xapianOmega = http://wiki.gandalf.adrift.space/search
+webPath = /
+
+[Wikis]
+public = http://wiki.gandalf.adrift.space/
+private = http://wiki.gandalf.adrift.space/private/
diff --git a/hs/vimwiki.cabal b/hs/vimwiki.cabal
index 74d497b..04d37c5 100644
--- a/hs/vimwiki.cabal
+++ b/hs/vimwiki.cabal
@@ -32,24 +32,29 @@ 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.9,
- data-default,
- directory,
- filepath,
- unix,
- pandoc,
- pandoc-types,
- text,
- containers,
- config-ini,
- optparse-applicative,
- blaze-markup,
- blaze-html,
- doctemplates,
- lens,
- network-uri,
- pretty-show,
- extra
+ blaze-html >= 0.9,
+ data-default >= 0.7,
+ directory >= 1.3.6,
+ filepath >= 1.4.2,
+ lens >= 5.1,
+ network-uri >= 2.6.4,
+ optparse-applicative >= 0.17,
+ pandoc >= 2.19,
+ pandoc-types >= 1.22,
+ 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)
default-language: Haskell2010