gdritter repos bricoleur / master src / Bricoleur / Commands / Splice.hs
master

Tree @master (Download .tar.gz)

Splice.hs @masterraw · history · blame

{-# LANGUAGE OverloadedStrings #-}

module Bricoleur.Commands.Splice (runSplice) where

import Bricoleur.Config
import Bricoleur.Opts
import Bricoleur.Utils
import Control.Monad (foldM)
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TL
import System.FilePath ((</>))
import qualified System.FilePath as Sys

findFragmentIdentifiers :: TL.Text -> [T.Text]
findFragmentIdentifiers t
  | (_, rs) <- TL.break (== '«') t,
    (f, rs') <- TL.break (== '»') (TL.drop 1 rs),
    not (TL.null f) =
    TL.toStrict f : findFragmentIdentifiers rs'
  | otherwise = []

newtype Fragment = Fragment [T.Text] deriving (Eq, Show)

toIdent :: T.Text -> TL.Text
toIdent = TL.fromStrict . T.cons '«' . flip T.snoc '»'

findFragments :: FilePath -> T.Text -> [Source] -> Either TL.Text (IO TL.Text)
findFragments root s sources = case T.splitOn "/" s of
  [] -> throw "Invalid empty fragment name!"
  (x : rs) -> case [ src
                     | src@Source {sourceName = n} <- sources,
                       n == x
                   ] of
    [src] -> go src rs (sourceExpose src)
    [] -> throw ("Unable to find source named " % stext) x
    _ -> throw ("Ambiguous source name: " % stext) x
  where
    go q [] (ExposeFile path) =
      return (cOpenFile "file source" (root </> sourceDir q </> path))
    go _ [] ExposeSections {} =
      throw
        ( "Splice identifier «" % stext
            % "» matches a file with sections"
        )
        s
    go _ [] NamedMap {} =
      throw
        ( "Splice identifier «" % stext
            % "» matches a map, but does not specify a key!"
        )
        s
    go q [section] (ExposeSections path) =
      return (readSection (root </> sourceDir q </> path) section)
    go _ (_ : _) (ExposeSections path) =
      throw
        ( "Splice identifier «" % stext
            % "» indexes too far into the path "
            % string
            % "!"
        )
        s
        path
    go _ (_ : _) (ExposeFile path) =
      throw
        ( "Splice identifier «" % stext
            % "» indexes too far into the file "
            % string
            % "!"
        )
        s
        path
    go q (k : rs) (NamedMap m)
      | Just e <- M.lookup k m = go q rs e
      | otherwise =
        throw
          ( "Splice identifier «" % stext
              % "» references a key "
              % stext
              % " which cannot be found."
          )
          s
          k

doReplacements :: FilePath -> [Source] -> TL.Text -> IO TL.Text
doReplacements root e original =
  foldM doReplace original (findFragmentIdentifiers original)
  where
    doReplace file frag = do
      new <- case findFragments root frag e of
        Left err -> cDie ("Fragment error: " % text) err
        Right x -> x
      return (TL.replace (toIdent frag) new file)

readSection :: FilePath -> T.Text -> IO TL.Text
readSection path f = do
  contents <- cOpenFile "section source" path
  case TL.breakOn (toIdent f) contents of
    (_, "") ->
      cDie
        ( "Unable to find section " % shown
            % " in file "
            % string
        )
        f
        path
    (_, rs) ->
      let (_, skipLine) = TL.breakOn "\n" rs
          (section, _) = TL.breakOn "«end»" (TL.drop 1 skipLine)
          (skipLine', _) = TL.breakOnEnd "\n" section
       in return (TL.init skipLine')

runSplice :: Config -> Options -> IO ()
runSplice conf opts = do
  let root = Sys.takeDirectory (optFile opts)
  f <- cOpenFile "document" (root </> confDocument conf)
  rs <- doReplacements root (confSources conf) f
  TL.putStrLn rs