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

Tree @master (Download .tar.gz)

Splice.hs @master

3a981ca
 
309076d
3a981ca
309076d
 
 
 
3a981ca
 
 
 
309076d
3a981ca
7045b4a
3a981ca
 
309076d
 
 
 
3a981ca
 
484fbb8
 
3a981ca
 
 
484fbb8
adada44
309076d
 
 
 
3a981ca
309076d
 
 
3a981ca
 
02b7c30
309076d
 
 
 
 
 
 
 
 
 
 
 
3a981ca
 
309076d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a981ca
309076d
 
 
 
 
 
 
 
 
3a981ca
 
 
 
 
 
 
cb77bdb
309076d
3a981ca
 
 
 
02b7c30
3a981ca
309076d
 
 
 
 
 
 
 
3a981ca
 
 
 
309076d
3a981ca
7045b4a
3a981ca
 
02b7c30
3a981ca
 
{-# 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