Splice.hs 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. {-# LANGUAGE OverloadedStrings #-}
  2. module Bricoleur.Commands.Splice(runSplice) where
  3. import Control.Monad (foldM)
  4. import qualified Data.Map as M
  5. import qualified Data.Text as T
  6. import qualified Data.Text.Lazy as TL
  7. import qualified Data.Text.Lazy.IO as TL
  8. import System.FilePath ((</>))
  9. import qualified System.FilePath as Sys
  10. import Bricoleur.Config
  11. import Bricoleur.Opts
  12. import Bricoleur.Utils
  13. findFragmentIdentifiers :: TL.Text -> [T.Text]
  14. findFragmentIdentifiers t
  15. | (_, rs) <- TL.break (== '«') t
  16. , (f, rs') <- TL.break (== '»') (TL.drop 1 rs)
  17. , not (TL.null f) = TL.toStrict f : findFragmentIdentifiers rs'
  18. | otherwise = []
  19. newtype Fragment = Fragment [T.Text] deriving (Eq, Show)
  20. toIdent :: T.Text -> TL.Text
  21. toIdent = TL.fromStrict . T.cons '«' . flip T.snoc '»'
  22. findFragments :: FilePath -> T.Text -> [Source] -> Either TL.Text (IO TL.Text)
  23. findFragments root s sources = case (T.splitOn "/" s) of
  24. [] -> throw "Invalid empty fragment name!"
  25. (x:rs) -> case [ src
  26. | src@Source { sourceName = n } <- sources
  27. , n == x
  28. ] of
  29. [src] -> go src rs (sourceExpose src)
  30. [] -> throw ("Unable to find source named " % stext) x
  31. _ -> throw ("Ambiguous source name: " % stext) x
  32. where
  33. go q [] (ExposeFile path) =
  34. return (cOpenFile "file source" (root </> sourceDir q </> path))
  35. go _ [] ExposeSections{} =
  36. throw ("Splice identifier «" % stext %
  37. "» matches a file with sections") s
  38. go _ [] NamedMap{} =
  39. throw ("Splice identifier «" % stext %
  40. "» matches a map, but does not specify a key!") s
  41. go q [section] (ExposeSections path) =
  42. return (readSection (root </> sourceDir q </> path) section)
  43. go _ (_:_) (ExposeSections path) =
  44. throw ("Splice identifier «" % stext %
  45. "» indexes too far into the path " % string % "!")
  46. s path
  47. go _ (_:_) (ExposeFile path) =
  48. throw ("Splice identifier «" % stext %
  49. "» indexes too far into the file " % string % "!")
  50. s path
  51. go q (k:rs) (NamedMap m)
  52. | Just e <- M.lookup k m = go q rs e
  53. | otherwise = throw
  54. ("Splice identifier «" % stext %
  55. "» references a key " % stext %
  56. " which cannot be found.") s k
  57. doReplacements :: FilePath -> [Source] -> TL.Text -> IO TL.Text
  58. doReplacements root e original =
  59. foldM doReplace original (findFragmentIdentifiers original)
  60. where
  61. doReplace file frag = do
  62. new <- case findFragments root frag e of
  63. Left err -> cDie ("Fragment error: " % text) err
  64. Right x -> x
  65. return (TL.replace (toIdent frag) new file)
  66. readSection :: FilePath -> T.Text -> IO TL.Text
  67. readSection path f = do
  68. contents <- cOpenFile "section source" path
  69. case TL.breakOn (toIdent f) contents of
  70. (_, "") -> cDie ("Unable to find section " % shown %
  71. " in file " % string) f path
  72. (_, rs) ->
  73. let (_, skipLine) = TL.breakOn "\n" rs
  74. (section, _) = TL.breakOn "«end»" (TL.drop 1 skipLine)
  75. (skipLine', _) = TL.breakOnEnd "\n" section
  76. in return (TL.init skipLine')
  77. runSplice :: Config -> Options -> IO ()
  78. runSplice conf opts = do
  79. let root = Sys.takeDirectory (optFile opts)
  80. f <- cOpenFile "document" (root </> confDocument conf)
  81. rs <- doReplacements root (confSources conf) f
  82. TL.putStrLn rs