diagrams-builder 0.3.0.1 → 0.4
raw patch · 4 files changed
+199/−130 lines, 4 filesdep +splitdep +transformersdep ~diagrams-cairodep ~diagrams-libdep ~diagrams-postscriptPVP ok
version bump matches the API change (PVP)
Dependencies added: split, transformers
Dependency ranges changed: diagrams-cairo, diagrams-lib, diagrams-postscript, diagrams-svg
API changes (from Hackage documentation)
- Diagrams.Builder: buildDiagram :: (Typeable b, Typeable v, InnerSpace v, OrderedField (Scalar v), Backend b v) => b -> v -> Options b v -> [String] -> String -> [String] -> [String] -> (String -> IO (x, Maybe (Options b v -> Options b v))) -> IO (BuildResult b v x)
+ Diagrams.Builder: buildDiagram :: (Typeable b, Typeable v, InnerSpace v, OrderedField (Scalar v), Backend b v, Show (Options b v)) => b -> v -> Options b v -> [String] -> String -> [String] -> [String] -> (String -> IO (x, Maybe (Options b v -> Options b v))) -> IO (BuildResult b v x)
Files
- CHANGES.markdown +12/−0
- LICENSE +4/−1
- diagrams-builder.cabal +12/−10
- src/Diagrams/Builder.hs +171/−119
CHANGES.markdown view
@@ -1,3 +1,15 @@+0.4: 9 August 2013+------------------++* Add hsenv compatibility.+* Big improvements in the way rebuilding is handled:+ - Strip comments before deciding whether to rebuild, so+ changing only comments does not trigger a rebuild+ - Take local imports into account: if a diagram has an import+ which corresponds to a local file, rebuild if that file has+ changed+ - Rebuild when options (e.g. size) change+ 0.3.0.1 (3 April 2013) ----------------------
LICENSE view
@@ -1,4 +1,7 @@-Copyright (c) 2012, Brent Yorgey+Copyright (c) 2012-2013 diagrams-builder team:++ Jeffrey Rosenbluth <jeffrey.rosenbluth@gmail.com>+ Brent Yorgey <byorgey@gmail.com> All rights reserved.
diagrams-builder.cabal view
@@ -1,5 +1,5 @@ name: diagrams-builder-version: 0.3.0.1+version: 0.4 synopsis: hint-based build service for the diagrams graphics EDSL. description: @diagrams-builder@ provides backend-agnostic tools for@@ -25,7 +25,7 @@ A LaTeX package, @diagrams-latex.sty@, is also provided in the @latex/@ directory of the source distribution, which renders diagrams code found- within @diagram@ environments. Note that + within @diagram@ environments. Note that @diagrams-latex.sty@ is licensed under the GPL. homepage: http://projects.haskell.org/diagrams license: BSD3@@ -47,10 +47,12 @@ Diagrams.Builder.Modules Diagrams.Builder.CmdLine build-depends: base >=4.2 && < 4.7,- diagrams-lib >=0.6 && < 0.7,+ diagrams-lib >=0.6 && < 0.8, hint ==0.3.*, directory, filepath,+ transformers >= 0.3 && < 0.4,+ split >= 0.2 && < 0.3, haskell-src-exts >= 1.13.1 && < 1.14, cryptohash >= 0.8 && < 0.10, bytestring >= 0.9.2 && < 0.11,@@ -92,8 +94,8 @@ filepath, directory, diagrams-builder,- diagrams-lib >= 0.6 && < 0.7,- diagrams-cairo >= 0.6 && < 0.7,+ diagrams-lib >= 0.6 && < 0.8,+ diagrams-cairo >= 0.6 && < 0.8, cmdargs >= 0.6 && < 0.11 executable diagrams-builder-svg@@ -111,8 +113,8 @@ filepath, directory, diagrams-builder,- diagrams-lib >= 0.6 && < 0.7,- diagrams-svg >= 0.6 && < 0.7,+ diagrams-lib >= 0.6 && < 0.8,+ diagrams-svg >= 0.6 && < 0.8, blaze-svg >= 0.3.3 && < 0.4, bytestring >= 0.9.2 && < 0.11, cmdargs >= 0.6 && < 0.11@@ -123,7 +125,7 @@ default-language: Haskell2010 other-extensions: DeriveDataTypeable RecordWildCards- + if !flag(ps) buildable: False @@ -132,6 +134,6 @@ filepath, directory, diagrams-builder,- diagrams-lib >= 0.6 && < 0.7,- diagrams-postscript >= 0.6 && < 0.7,+ diagrams-lib >= 0.6 && < 0.8,+ diagrams-postscript >= 0.6 && < 0.8, cmdargs >= 0.6 && < 0.11
src/Diagrams/Builder.hs view
@@ -1,8 +1,8 @@-{-# LANGUAGE StandaloneDeriving- , DeriveDataTypeable- , ScopedTypeVariables- , FlexibleContexts- #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -----------------------------------------------------------------------------@@ -37,23 +37,33 @@ ) where -import Diagrams.Builder.Modules-import Diagrams.Builder.CmdLine--import Diagrams.Prelude hiding ((<.>), e)--import Language.Haskell.Exts (prettyPrint)-import Language.Haskell.Interpreter hiding (ModuleName)+import Control.Monad (guard, mplus, mzero)+import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)+import Crypto.Hash (Digest, MD5,+ digestToHexByteString, hash)+import qualified Data.ByteString.Char8 as B+import Data.List (nub)+import Data.List.Split (splitOn)+import Data.Maybe (catMaybes, fromMaybe)+import Data.Typeable (Typeable)+import System.Directory (doesFileExist,+ getDirectoryContents,+ getTemporaryDirectory,+ removeFile)+import System.FilePath (takeBaseName, (<.>), (</>))+import System.IO (hClose, hPutStr, openTempFile) -import System.IO-import System.FilePath-import System.Directory+import Language.Haskell.Exts (ImportDecl, Module (..),+ ModuleName (..), importModule,+ prettyPrint)+import Language.Haskell.Interpreter hiding (ModuleName) -import Crypto.Hash (Digest, MD5, digestToHexByteString, hash)+import Diagrams.Builder.CmdLine+import Diagrams.Builder.Modules+import Diagrams.Prelude hiding (e, (<.>))+import System.Environment (getEnvironment)+import Language.Haskell.Interpreter.Unsafe (unsafeRunInterpreterWithArgs) -import qualified Data.ByteString.Char8 as B-import Data.List (nub)-import Data.Typeable deriving instance Typeable Any ------------------------------------------------------------@@ -67,15 +77,18 @@ -- | Set up the module to be interpreted, in the context of the -- necessary imports.-setDiagramImports :: MonadInterpreter m- => String -- ^ Filename of the module containing the diagrams- -> [String] -- ^ Additional necessary- -- imports. @Prelude@,- -- @Diagrams.Prelude@,- -- @Diagrams.Core.Types@,- -- and @Data.Monoid@ are included- -- by default.- -> m ()+setDiagramImports+ :: MonadInterpreter m+ => String+ -- ^ Filename of the module containing the diagrams++ -> [String]+ -- ^ Additional necessary imports. @Prelude@, @Diagrams.Prelude@,+ -- @Diagrams.Core.Types@, and @Data.Monoid@ are included by+ -- default.++ -> m ()+ setDiagramImports m imps = do loadModules [m] setTopLevelModules [takeBaseName m]@@ -86,21 +99,31 @@ ] ++ imps +getHsenvArgv :: IO [String]+getHsenvArgv = do+ env <- getEnvironment+ return $ case (lookup "HSENV" env) of+ Nothing -> []+ _ -> hsenvArgv+ where hsenvArgv = words $ fromMaybe "" (lookup "PACKAGE_DB_FOR_GHC" env)+ -- | Interpret a diagram expression based on the contents of a given -- source file, using some backend to produce a result.-interpretDiagram :: forall b v.- ( Typeable b, Typeable v- , InnerSpace v, OrderedField (Scalar v), Backend b v- )- => b -- ^ Backend token- -> v -- ^ Dummy vector to identify the vector space- -> Options b v -- ^ Rendering options- -> FilePath -- ^ Filename of the module containing the example- -> [String] -- ^ Additional imports needed- -> String -- ^ Expression of type @Diagram b v@ to be compiled- -> IO (Either InterpreterError (Result b v))-interpretDiagram b _ opts m imps dexp =- runInterpreter $ do+interpretDiagram+ :: forall b v.+ ( Typeable b, Typeable v+ , InnerSpace v, OrderedField (Scalar v), Backend b v+ )+ => b -- ^ Backend token+ -> v -- ^ Dummy vector to identify the vector space+ -> Options b v -- ^ Rendering options+ -> FilePath -- ^ Filename of the module containing the example+ -> [String] -- ^ Additional imports needed+ -> String -- ^ Expression of type @Diagram b v@ to be compiled+ -> IO (Either InterpreterError (Result b v))+interpretDiagram b _ opts m imps dexp = do+ args <- liftIO getHsenvArgv+ unsafeRunInterpreterWithArgs args $ do setDiagramImports m imps d <- interpret dexp (as :: Diagram b v) return (renderDia b opts d)@@ -131,63 +154,60 @@ -- module and interpreting the given expression. Can return either -- a parse error if the source does not parse, an interpreter error, -- or the final result.-buildDiagram :: ( Typeable b, Typeable v- , InnerSpace v, OrderedField (Scalar v), Backend b v- )- => b -- ^ Backend token- -> v -- ^ Dummy vector to fix the vector type- -> Options b v -- ^ Backend-specific options to use- -> [String] -- ^ Source code snippets. Each should- -- be a syntactically valid Haskell- -- module. They will be combined- -- intelligently, /i.e./ not just- -- pasted together textually but- -- combining pragmas, imports,- -- /etc./ separately.- -> String -- ^ Diagram expression to interpret- -> [String] -- ^ Extra @LANGUAGE@ pragmas to use- -- (@NoMonomorphismRestriction@ is used- -- by default.)- -> [String] -- ^ Additional imports- -- ("Diagrams.Prelude" is imported by- -- default).- -> (String -> IO (x, Maybe (Options b v -> Options b v)))- -- ^ A function to decide whether a- -- particular diagram needs to be- -- regenerated. It will be passed- -- the final assembled source for the- -- diagram (but with the module name- -- set to @Main@ instead of something- -- auto-generated, so that hashing- -- the source will produce consistent- -- results across runs). It can- -- return some information (such as a- -- hash of the source) via the @x@- -- result, which will be passed- -- through to the result of- -- 'buildDiagram'. More importantly,- -- it decides whether the diagram- -- should be built: a result of- -- 'Just' means the diagram /should/- -- be built; 'Nothing' means it- -- should not. In the case that it- -- should be built, it returns a- -- function for updating the- -- rendering options. This could be- -- used, /e.g./, to request a- -- filename based on a hash of the- -- source.- --- -- Two standard decision functions- -- are provided for convenience:- -- 'alwaysRegenerate' returns no- -- extra information and always- -- decides to regenerate the diagram;- -- 'hashedRegenerate' creates a hash- -- of the diagram source and looks- -- for a file with that name in a- -- given directory.- -> IO (BuildResult b v x)+buildDiagram+ :: ( Typeable b, Typeable v+ , InnerSpace v, OrderedField (Scalar v), Backend b v+ , Show (Options b v)+ )+ => b+ -- ^ Backend token++ -> v+ -- ^ Dummy vector to fix the vector type++ -> Options b v+ -- ^ Backend-specific options to use++ -> [String]+ -- ^ Source code snippets. Each should be a syntactically valid+ -- Haskell module. They will be combined intelligently, /i.e./+ -- not just pasted together textually but combining pragmas,+ -- imports, /etc./ separately.++ -> String+ -- ^ Diagram expression to interpret++ -> [String]+ -- ^ Extra @LANGUAGE@ pragmas to use (@NoMonomorphismRestriction@+ -- is used by default.)++ -> [String]+ -- ^ Additional imports ("Diagrams.Prelude" is imported by+ -- default).++ -> (String -> IO (x, Maybe (Options b v -> Options b v)))+ -- ^ A function to decide whether a particular diagram needs to+ -- be regenerated. It will be passed the final assembled+ -- source for the diagram (but with the module name set to+ -- @Main@ instead of something auto-generated, so that hashing+ -- the source will produce consistent results across runs). It+ -- can return some information (such as a hash of the source)+ -- via the @x@ result, which will be passed through to the+ -- result of 'buildDiagram'. More importantly, it decides+ -- whether the diagram should be built: a result of 'Just'+ -- means the diagram /should/ be built; 'Nothing' means it+ -- should not. In the case that it should be built, it returns+ -- a function for updating the rendering options. This could+ -- be used, /e.g./, to request a filename based on a hash of+ -- the source.+ --+ -- Two standard decision functions are provided for+ -- convenience: 'alwaysRegenerate' returns no extra information+ -- and always decides to regenerate the diagram;+ -- 'hashedRegenerate' creates a hash of the diagram source and+ -- looks for a file with that name in a given directory.++ -> IO (BuildResult b v x) buildDiagram b v opts source dexp langs imps shouldRegen = do let source' = map unLit source case createModule@@ -196,8 +216,9 @@ ("Diagrams.Prelude" : imps) source' of Left err -> return (ParseErr err)- Right m -> do- regen <- shouldRegen (prettyPrint m ++ dexp)+ Right m@(Module _ _ _ _ _ srcImps _) -> do+ liHashes <- getLocalImportHashes srcImps+ regen <- shouldRegen (prettyPrint m ++ dexp ++ show opts ++ concat liHashes) case regen of (info, Nothing) -> return $ Skipped info (info, Just upd) -> do@@ -211,6 +232,39 @@ removeFile tmp return $ either InterpErr (OK info) compilation +-- | Take a list of imports, and return hashes of the contents of+-- those imports which are local. Note, this only finds imports+-- which exist relative to the current directory, which is not as+-- general as it probably should be --- we could be calling+-- 'buildDiagram' on source code which lives anywhere.+getLocalImportHashes :: [ImportDecl] -> IO [String]+getLocalImportHashes+ = (fmap . map) hashStr+ . fmap catMaybes+ . mapM getLocalSource+ . map (foldr1 (</>) . splitOn "." . getModuleName . importModule)++-- | Given a relative path with no extension, like+-- @\"Foo\/Bar\/Baz\"@, check whether such a file exists with either+-- a @.hs@ or @.lhs@ extension; if so, return its /pretty-printed/+-- contents (removing all comments, canonicalizing formatting, /etc./).+getLocalSource :: FilePath -> IO (Maybe String)+getLocalSource f = runMaybeT $ do+ contents <- getLocal f+ case (doModuleParse . unLit) contents of+ Left _ -> mzero+ Right m -> return (prettyPrint m)++-- | Given a relative path with no extension, like+-- @\"Foo\/Bar\/Baz\"@, check whether such a file exists with either a+-- @.hs@ or @.lhs@ extension; if so, return its contents.+getLocal :: FilePath -> MaybeT IO String+getLocal m = tryExt "hs" `mplus` tryExt "lhs"+ where+ tryExt ext = do+ let f = m <.> ext+ liftIO (doesFileExist f) >>= guard >> liftIO (readFile f)+ -- | Convenience function suitable to be given as the final argument -- to 'buildDiagram'. It implements the simple policy of always -- rebuilding every diagram.@@ -227,25 +281,23 @@ -- hash. (Most likely, one would want to set the requested output -- file to the hash followed by some extension.) It also returns -- the generated hash.-hashedRegenerate :: (String -> a -> a) -- ^ A function for computing- -- an update to rendering- -- options, given a new base- -- filename computed from a- -- hash of the diagram- -- source.- -> FilePath -- ^ The directory in which to- -- look for generated files- -> String -- ^ The \"source\" to- -- hash. Note that this does- -- not actually have to be- -- valid source code. A- -- common trick is to- -- concatenate the actual- -- source code with String- -- representations of any- -- other information on which- -- the diagram depends.- -> IO (String, Maybe (a -> a))+hashedRegenerate+ :: (String -> a -> a)+ -- ^ A function for computing an update to rendering options,+ -- given a new base filename computed from a hash of the+ -- diagram source.++ -> FilePath+ -- ^ The directory in which to look for generated files++ -> String+ -- ^ The \"source\" to hash. Note that this does not actually+ -- have to be valid source code. A common trick is to+ -- concatenate the actual source code with String representations+ -- of any other information on which the diagram depends.++ -> IO (String, Maybe (a -> a))+ hashedRegenerate upd dir src = do let fileBase = hashStr src files <- getDirectoryContents dir