dhall-bash (empty) → 1.0.0
raw patch · 5 files changed
+456/−0 lines, 5 filesdep +basedep +bytestringdep +containerssetup-changed
Dependencies added: base, bytestring, containers, dhall, dhall-bash, neat-interpolation, optparse-generic, shell-escape, text, text-format, trifecta, vector
Files
- LICENSE +24/−0
- Setup.hs +2/−0
- dhall-bash.cabal +54/−0
- exec/Main.hs +74/−0
- src/Dhall/Bash.hs +302/−0
+ LICENSE view
@@ -0,0 +1,24 @@+Copyright (c) 2017 Gabriel Gonzalez+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:+ * Redistributions of source code must retain the above copyright notice,+ this list of conditions and the following disclaimer.+ * Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.+ * Neither the name of Gabriel Gonzalez nor the names of other contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ dhall-bash.cabal view
@@ -0,0 +1,54 @@+Name: dhall-bash+Version: 1.0.0+Cabal-Version: >=1.8.0.2+Build-Type: Simple+Tested-With: GHC == 7.10.2, GHC == 8.0.1+License: BSD3+License-File: LICENSE+Copyright: 2017 Gabriel Gonzalez+Author: Gabriel Gonzalez+Maintainer: Gabriel439@gmail.com+Bug-Reports: https://github.com/Gabriel439/Haskell-Dhall-Bash-Library/issues+Synopsis: Compile Dhall to Bash+Description:+ Use this package if you want to compile Dhall expressions to Bash.+ You can use this package as a library or an executable:+ .+ * See the "Dhall.Bash" module if you want to use this package as a library+ .+ * Use the @dhall-to-bash@ if you want an executable+ .+ The "Dhall.Bash" module also contains instructions for how to use this+ package+Category: Compiler+Source-Repository head+ Type: git+ Location: https://github.com/Gabriel439/Haskell-Dhall-Bash-Library++Library+ Hs-Source-Dirs: src+ Build-Depends:+ base >= 4.8.0.0 && < 5 ,+ bytestring < 0.11,+ containers < 0.6 ,+ dhall >= 1.0.1 && < 1.3 ,+ neat-interpolation < 0.4 ,+ shell-escape < 0.3 ,+ text-format < 0.4 ,+ text >= 0.2 && < 1.3 ,+ vector >= 0.3 && < 0.13+ Exposed-Modules: Dhall.Bash+ GHC-Options: -Wall++Executable dhall-to-bash+ Hs-Source-Dirs: exec+ Main-Is: Main.hs+ Build-Depends:+ base >= 4.8.0.0 && < 5 ,+ bytestring < 0.11,+ dhall >= 1.0.1 && < 1.3 ,+ dhall-bash ,+ optparse-generic >= 1.1.1 && < 1.2 ,+ trifecta >= 1.6 && < 1.7 ,+ text >= 0.11.1.0 && < 1.3+ GHC-Options: -Wall
+ exec/Main.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeOperators #-}++module Main where++import Control.Exception (SomeException)+import Data.ByteString (ByteString)+import Options.Generic (Generic, ParseRecord, type (<?>)(..))+import System.Exit (ExitCode(..))+import Text.Trifecta.Delta (Delta(..))++import qualified Control.Exception+import qualified Data.ByteString+import qualified Data.Text.Lazy.IO+import qualified Dhall+import qualified Dhall.Bash+import qualified Dhall.Import+import qualified Dhall.Parser+import qualified Dhall.TypeCheck+import qualified GHC.IO.Encoding+import qualified Options.Generic+import qualified System.Exit+import qualified System.IO++data Options = Options+ { explain :: Bool+ <?> "Explain error messages in detail"+ , declare :: Maybe ByteString+ <?> "Declare the given variable as a statement instead of an expression"+ } deriving (Generic, ParseRecord)++main :: IO ()+main = handle (do+ GHC.IO.Encoding.setLocaleEncoding GHC.IO.Encoding.utf8+ Options {..} <- Options.Generic.getRecord "Compile Dhall to Bash"++ (if unHelpful explain then Dhall.detailed else id) (do+ inText <- Data.Text.Lazy.IO.getContents++ expr <- case Dhall.Parser.exprFromText (Directed "(stdin)" 0 0 0 0) inText of+ Left err -> Control.Exception.throwIO err+ Right expr -> return expr++ expr' <- Dhall.Import.load expr+ case Dhall.TypeCheck.typeOf expr' of+ Left err -> Control.Exception.throwIO err+ Right _ -> return ()++ bytes <- case unHelpful declare of+ Nothing -> do+ case Dhall.Bash.dhallToExpression expr' of+ Left err -> Control.Exception.throwIO err+ Right bytes -> return bytes+ Just var -> do+ case Dhall.Bash.dhallToStatement expr' var of+ Left err -> Control.Exception.throwIO err+ Right bytes -> return bytes+ Data.ByteString.putStr bytes ))++handle :: IO a -> IO a+handle = Control.Exception.handle handler+ where+ handler :: SomeException -> IO a+ handler e = case Control.Exception.fromException e of+ Just ExitSuccess -> do+ Control.Exception.throwIO e+ _ -> do+ System.IO.hPutStrLn System.IO.stderr ""+ System.IO.hPrint System.IO.stderr e+ System.Exit.exitFailure
+ src/Dhall/Bash.hs view
@@ -0,0 +1,302 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++{-| This library exports two utilities for compiling Dhall expressions to Bash:++ * `dhallToExpression`, which emits a Bash expression (i.e. a valid+ right-hand side for an assignment)++ * `dhallToStatement`, which emits a Bash @declare@ or @unset@ statement+ suitable for use with `eval`++ `dhallToExpression` only supports the conversion of primitive values, such+ as:++ * @Bool@ - which translates to a string that is either @"true"@ or @"false"@+ * @Natural@ - which translates to a Bash integer+ * @Integer@ - which translates to a Bash integer+ * @Text@ - which translates to a Bash string (properly escaped if necessary)++ The @dhall-to-bash@ executable by default tries to compile Dhall expressions+ to Bash expressions using the `dhallToExpression` function. For example:++> $ dhall-to-bash <<< 'True'+> true+> $ dhall-to-bash <<< 'False'+> false+> $ dhall-to-bash <<< '1'+> 1+> $ dhall-to-bash <<< '+1'+> 1+> $ dhall-to-bash <<< '"ABC"'+> ABC+> $ dhall-to-bash <<< '" X "'+> $' X '+> $ dhall-to-bash <<< 'Natural/even +100'+> true++ The output of `dhallToExpression` is a valid Bash expression that can be+ embedded anywhere Bash expressions are valid, such as the right-hand side of+ an assignment statement:++> $ FOO=$(dhall-to-bash <<< 'List/length Integer [1, 2, 3]')+> $ echo "${FOO}"+> 3++ `dhallToStatement` supports a wider range of expressions by also adding+ support for:++ * @Optional@ - which translates to a variable which is either set or unset+ * @List@ - which translates to a Bash array+ * records - which translate to Bash associative arrays++ The @dhall-to-bash@ executable can emit a statement instead of an expression+ if you add the @--declare@ flag specifying which variable to set or unset.+ For example:++> $ dhall-to-bash --declare FOO <<< '[] : Optional Integer'+> unset FOO+> $ dhall-to-bash --declare FOO <<< '[1] : Optional Integer'+> declare -r -i FOO=1+> $ dhall-to-bash --declare FOO <<< '[[1] : Optional Integer] : Optional (Optional Integer)'+> declare -r -i FOO=1+> $ dhall-to-bash --declare FOO <<< '[[] : Optional Integer] : Optional (Optional Integer)'+> unset FOO+> $ dhall-to-bash --declare FOO <<< '[1, 2, 3]'+> declare -r -a FOO=(1 2 3)+> $ dhall-to-bash --declare FOO <<< '{ bar = 1, baz = True }'+> declare -r -A FOO=([bar]=1 [baz]=true)++ The output of `dhallToExpression` is either a @declare@ or @unset@ Bash+ statement that you can pass to @eval@:++> $ eval $(dhall-to-bash --declare FOO <<< '{ bar = 1, baz = True }')+> $ echo "${FOO[bar]}"+> 1+> $ echo "${FOO[baz]}"+> true++ @dhall-to-bash@ declares variables read-only (i.e. @-r@) to prevent you from+ accidentally overwriting, deleting or mutating variables:++> $ eval $(dist/build/dhall-to-bash/dhall-to-bash --declare BAR <<< '1')+> $ echo "${BAR"}+> 1+> $ unset BAR+> bash: unset: BAR: cannot unset: readonly variable+> $ eval $(dist/build/dhall-to-bash/dhall-to-bash --declare BAR <<< '2')+> bash: declare: BAR: readonly variable++-}++module Dhall.Bash (+ -- * Dhall to Bash+ dhallToExpression+ , dhallToStatement++ -- * Exceptions+ , ExpressionError(..)+ , StatementError(..)+ ) where++import Control.Exception (Exception)+import Data.Bifunctor (first)+import Data.ByteString+import Data.Monoid ((<>))+import Data.Typeable (Typeable)+import Dhall.Core (Expr(..))+import Dhall.TypeCheck++import qualified Data.Foldable+import qualified Data.Map+import qualified Data.Text+import qualified Data.Text.Buildable+import qualified Data.Text.Encoding+import qualified Data.Text.Lazy+import qualified Data.Text.Lazy.Builder+import qualified Data.Vector+import qualified Dhall.Core+import qualified NeatInterpolation+import qualified Text.ShellEscape++_ERROR :: Data.Text.Text+_ERROR = "\ESC[1;31mError\ESC[0m"++{-| This is the exception type for errors that might arise when translating+ Dhall expressions to Bash statements++ Because the majority of Dhall language features do not easily translate to+ Bash this just returns the expression that failed+-}+data StatementError+ = UnsupportedStatement (Expr X X)+ | UnsupportedSubexpression (Expr X X)+ deriving (Typeable)++instance Show StatementError where+ show (UnsupportedStatement e) =+ Data.Text.unpack [NeatInterpolation.text|+$_ERROR: Cannot translate to a Bash statement++Explanation: Only primitive values, records, ❰List❱s, and ❰Optional❱ values can+be translated from Dhall to a Bash statement++The following Dhall expression could not be translated to a Bash statement:++↳ $txt+|]+ where+ txt = Data.Text.Lazy.toStrict (Dhall.Core.pretty e)+ show (UnsupportedSubexpression e) =+ -- Carefully note: No tip suggesting `--declare` since it won't work+ -- here (and the user is already using `--declare`)+ Data.Text.unpack [NeatInterpolation.text|+$_ERROR: Cannot translate to a Bash expression++Explanation: Only primitive values can be translated from Dhall to a Bash+expression++The following Dhall expression could not be translated to a Bash expression:++↳ $txt+|]+ where+ txt = Data.Text.Lazy.toStrict (Dhall.Core.pretty e)++instance Exception StatementError++{-| This is the exception type for errors that might arise when translating+ Dhall expressions to Bash expressions++ Because the majority of Dhall language features do not easily translate to+ Bash this just returns the expression that failed+-}+data ExpressionError = UnsupportedExpression (Expr X X) deriving (Typeable)++instance Show ExpressionError where+ show (UnsupportedExpression e) =+ Data.Text.unpack [NeatInterpolation.text|+$_ERROR: Cannot translate to a Bash expression++Explanation: Only primitive values can be translated from Dhall to a Bash+expression++The following Dhall expression could not be translated to a Bash expression:++↳ $txt$tip+|]+ where+ txt = Data.Text.Lazy.toStrict (Dhall.Core.pretty e)++ tip = case e of+ OptionalLit _ _ -> "\n\n" <> [NeatInterpolation.text|+Tip: You can convert an ❰Optional❱ value to a Bash statement using the --declare+flag+|]+ ListLit _ _ -> "\n\n" <> [NeatInterpolation.text|+Tip: You can convert a ❰List❱ to a Bash statement using the --declare flag+|]+ RecordLit _ -> "\n\n" <> [NeatInterpolation.text|+Tip: You can convert a record to a Bash statement using the --declare flag+|]+ _ -> ""++instance Exception ExpressionError++{-| Compile a Dhall expression to a Bash statement that @declare@s or @unset@s a+ a variable of your choice++ This only supports:++ * @Bool@s+ * @Natural@s+ * @Integer@s+ * @Text@s+ * @Optional@s+ * @List@s+ * records+ -}+dhallToStatement+ :: Expr s X+ -- ^ Dhall expression to compile+ -> ByteString+ -- ^ Variable to @declare@ or @unset@+ -> Either StatementError ByteString+ -- ^ Bash statement or compile failure+dhallToStatement expr0 var0 = go (Dhall.Core.normalize expr0)+ where+ var = Text.ShellEscape.bytes (Text.ShellEscape.bash var0)++ adapt (UnsupportedExpression e) = UnsupportedSubexpression e++ go (BoolLit a) = do+ go (TextLit (if a then "true" else "false"))+ go (NaturalLit a) = do+ go (IntegerLit (fromIntegral a))+ go (IntegerLit a) = do+ e <- first adapt (dhallToExpression (IntegerLit a))+ let bytes = "declare -r -i " <> var <> "=" <> e+ return bytes+ go (TextLit a) = do+ e <- first adapt (dhallToExpression (TextLit a))+ let bytes = "declare -r " <> var <> "=" <> e+ return bytes+ go (ListLit _ bs) = do+ bs' <- first adapt (mapM dhallToExpression bs)+ let bytes+ = "declare -r -a "+ <> var+ <> "=("+ <> Data.ByteString.intercalate " " (Data.Foldable.toList bs')+ <> ")"+ return bytes+ go (OptionalLit _ bs) = do+ if Data.Vector.null bs+ then do+ let bytes = "unset " <> var+ return bytes+ else go (Data.Vector.head bs)+ go (RecordLit a) = do+ let process (k, v) = do+ v' <- dhallToExpression v+ let bytes = Data.Text.Encoding.encodeUtf8 (Data.Text.Lazy.toStrict k)+ let k' = Text.ShellEscape.bytes (Text.ShellEscape.bash bytes)+ return ("[" <> k' <> "]=" <> v')+ kvs' <- first adapt (traverse process (Data.Map.toList a))+ let bytes+ = "declare -r -A "+ <> var+ <> "=("+ <> Data.ByteString.intercalate " " kvs'+ <> ")"+ return bytes+ go e = Left (UnsupportedStatement e)++{-| Compile a Dhall expression to a Bash expression++ This only supports:++ * @Bool@s+ * @Natural@s+ * @Integer@s+ * @Text@s+ -}+dhallToExpression+ :: Expr s X+ -- ^ Dhall expression to compile+ -> Either ExpressionError ByteString+ -- ^ Bash expression or compile failure+dhallToExpression expr0 = go (Dhall.Core.normalize expr0)+ where+ go (BoolLit a) = do+ go (TextLit (if a then "true" else "false"))+ go (NaturalLit a) = do+ go (IntegerLit (fromIntegral a))+ go (IntegerLit a) = do+ go (TextLit (Data.Text.Buildable.build a))+ go (TextLit a) = do+ let text = Data.Text.Lazy.Builder.toLazyText a+ let bytes = Data.Text.Encoding.encodeUtf8 (Data.Text.Lazy.toStrict text)+ return (Text.ShellEscape.bytes (Text.ShellEscape.bash bytes))+ go e = Left (UnsupportedExpression e)