Skip to content

Add cabal scripting support #5483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 2 additions & 27 deletions Cabal/Distribution/PackageDescription/Parsec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import Distribution.Parsec.Newtypes (CommaFSep, List, SpecVersio
import Distribution.Parsec.Parser
import Distribution.Parsec.ParseResult
import Distribution.Pretty (prettyShow)
import Distribution.Simple.Utils (die', fromUTF8BS, warn)
import Distribution.Simple.Utils (fromUTF8BS)
import Distribution.Text (display)
import Distribution.Types.CondTree
import Distribution.Types.Dependency (Dependency)
Expand All @@ -70,7 +70,6 @@ import Distribution.Verbosity (Verbosity)
import Distribution.Version
(LowerBound (..), Version, asVersionIntervals, mkVersion, orLaterVersion, version0,
versionNumbers)
import System.Directory (doesFileExist)

import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
Expand All @@ -83,31 +82,7 @@ import qualified Text.Parsec as P

-- ---------------------------------------------------------------
-- Parsing

-- | Helper combinator to do parsing plumbing for files.
--
-- Given a parser and a filename, return the parse of the file,
-- after checking if the file exists.
--
-- Argument order is chosen to encourage partial application.
readAndParseFile
:: (BS.ByteString -> ParseResult a) -- ^ File contents to final value parser
-> Verbosity -- ^ Verbosity level
-> FilePath -- ^ File to read
-> IO a
readAndParseFile parser verbosity fpath = do
exists <- doesFileExist fpath
unless exists $
die' verbosity $
"Error Parsing: file \"" ++ fpath ++ "\" doesn't exist. Cannot continue."
bs <- BS.readFile fpath
let (warnings, result) = runParseResult (parser bs)
traverse_ (warn verbosity . showPWarning fpath) warnings
case result of
Right x -> return x
Left (_, errors) -> do
traverse_ (warn verbosity . showPError fpath) errors
die' verbosity $ "Failed parsing \"" ++ fpath ++ "\"."
-- ---------------------------------------------------------------

-- | Parse the given package file.
readGenericPackageDescription :: Verbosity -> FilePath -> IO GenericPackageDescription
Expand Down
43 changes: 42 additions & 1 deletion Cabal/Distribution/Parsec/ParseResult.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ module Distribution.Parsec.ParseResult (
parseFatalFailure',
getCabalSpecVersion,
setCabalSpecVersion,
readAndParseFile,
parseString
) where

import qualified Data.ByteString.Char8 as BS
import Distribution.Compat.Prelude
import Distribution.Parsec.Common
(PError (..), PWarnType (..), PWarning (..), Position (..), zeroPos)
( PError (..), PWarnType (..), PWarning (..), Position (..), zeroPos
, showPWarning, showPError)
import Distribution.Simple.Utils (die', warn)
import Distribution.Verbosity (Verbosity)
import Distribution.Version (Version)
import Prelude ()
import System.Directory (doesFileExist)

#if MIN_VERSION_base(4,10,0)
import Control.Applicative (Applicative (..))
Expand Down Expand Up @@ -140,3 +147,37 @@ parseFatalFailure' = PR pr
pr s failure _success = failure s

err = PError zeroPos "Unknown fatal error"

-- | Helper combinator to do parsing plumbing for files.
--
-- Given a parser and a filename, return the parse of the file,
-- after checking if the file exists.
--
-- Argument order is chosen to encourage partial application.
readAndParseFile
:: (BS.ByteString -> ParseResult a) -- ^ File contents to final value parser
-> Verbosity -- ^ Verbosity level
-> FilePath -- ^ File to read
-> IO a
readAndParseFile parser verbosity fpath = do
exists <- doesFileExist fpath
unless exists $
die' verbosity $
"Error Parsing: file \"" ++ fpath ++ "\" doesn't exist. Cannot continue."
bs <- BS.readFile fpath
parseString parser verbosity fpath bs

parseString
:: (BS.ByteString -> ParseResult a) -- ^ File contents to final value parser
-> Verbosity -- ^ Verbosity level
-> String -- ^ File name
-> BS.ByteString
-> IO a
parseString parser verbosity name bs = do
let (warnings, result) = runParseResult (parser bs)
traverse_ (warn verbosity . showPWarning name) warnings
case result of
Right x -> return x
Left (_, errors) -> do
traverse_ (warn verbosity . showPError name) errors
die' verbosity $ "Failed parsing \"" ++ name ++ "\"."
23 changes: 23 additions & 0 deletions Cabal/doc/nix-local-build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,29 @@ have to separate them with ``--``.

$ cabal new-run target -- -a -bcd --argument

'new-run' also supports running script files that use a certain format. With
a script that looks like:

::

#!/usr/bin/env cabal
{- cabal:
build-depends: base ^>= 4.11
, shelly ^>= 1.8.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs default-language: Haskell2010 as well, since otherwise you always get a warning. Unless we decide to splice that in automagically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@23Skidoo actually this makes me think of something... I think we should encode the spec-version somehow... e.g.

#!/usr/bin/env cabal
{-# LANGUAGE Haskell2010, GADTs #-}
{- cabal:2.4
build-depends: ...
-}

and we might want to tolerate not having to specify default-language w/ scripts, as default-{extensions,language} makes less sense imho for scripts, as we have actual language pragmas (I've intentionally added one in the example above), and you certainly wouldn't want to replicate those...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about multi-file scripts? Easier to set default-language in one place than use a pragma in each file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@23Skidoo we can still support default-language; I just think we shouldn't force people to use it by emitting nasty warnings like we do for .cabal files...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It skips that check on purpose.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't we then need to parse LANGUAGE pragmas to make sure that they are actually specified when default-language is absent?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@23Skidoo depends on the semantics you want to have for scripts...

However, I suggest we postpone this design/bikeshedding issue to a followup-task in order not to hold up merging this PR; I think all @typedrat needs to do is tweaks the docs and we're ready to go?

-}

main :: IO ()
main = do
...

It can either be executed like any other script, using ``cabal`` as an
interpreter, or through this command:

::

$ cabal new-run script.hs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please also add an example of how to pass command-line arguments to a script started with new-run? I expect something like cabal new-run script.hs -- --foo bar.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's exactly right, I thought it was regular enough with the rest of new-run that it didn't need to be called out explicitly, but you're right that it could use a mention.

$ cabal new-run script.hs -- --arg1 # args are passed like this

cabal new-freeze
----------------

Expand Down
Loading