shake-plus: Re-export of Shake using well-typed paths and ReaderT.

[ development, library, mit, shake ] [ Propose Tags ]

Re-export of Shake using well-typed paths and ReaderT. You can thread logging through your Shake Actions, and better keep track of source and output folders using the Within type.

[Skip to Readme]
Versions [faq],,,,,,,,,,,,,,,,,,,,,,,,
Change log
Dependencies aeson (>=, base (>=4.7 && <5), comonad, extra, path, rio, shake, within [details]
License MIT
Copyright 2020 Daniel Firth
Author Daniel Firth
Category development, shake
Home page
Uploaded by locallycompact at 2020-06-30T11:50:59Z
Distributions NixOS:
Downloads 3567 total (1244 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs available [build log]
Last success reported on 2020-06-30 [all 1 reports]


[Index] [Quick Jump]


Maintainer's Corner

For package maintainers and hackage trustees

Readme for shake-plus-

[back to package description]

Shake+ - Super Powered Shake

Attempt at a batteries included Shake. We reexport replacements for the main utility functions of Shake with the following adjustments whereever possible.

  • Well-typed paths using the path
  • New type classes MonadAction, MonadUnliftAction and MonadRules with concrete ReaderT transformers:
    • RAction r a = RAction (ReaderT r Action a) and
    • ShakePlus r a = ShakePlus (ReaderT r Rules a)
  • Text instead of String wherever it is appropriate.
  • within style variants of the standard file and directory operations that in some cases return or accept Within b (Path Rel File) values to keep tags of parent directories.

This is an early release and some things may be missing or broken, but so far the conveniences have been worth it. Some notes on the approach are detailed below.


Using the path library is kind of a no brainer. I lose a lot of time to problems that could be avoided by using this library, so it's everywhere. The names for these functions shadow the existing names, so you may want to import qualified Development.Shake while this library progresses if you have other FilePath based Shake rules that you want to mix into your build.

The standard Development.Shake.FilePath functions for directory manipulation are not re-exported in full, and you should use the functions in the path library (such as replaceExtension) and other path-based libraries. This will probably change.

FilePatterns are kept as-is, as Path is strongly normalizing it makes sense to keep these as Strings.


The ReaderT r Action a transformer (called RAction) is similar to the RIO type and should be used similarly. In fact, you can reuse the logging functions from RIO within any RAction block, which is one of the main motivators for having an Action which is also a MonadReader. If you need to reuse an existing shake Action in an RAction, use liftAction.

Using Within

One common complaint about Shake is having to keep track of source and output directories and translating FilePaths when using the input to an Action, leading to lots of repetition of the form (sourceFolder </>) . (-<.> ".ext") . dropDirectory1 which is prone to breakage. Using Path helps this to some degree, but in some cases is even more annoying because lots of Path functions use MonadThrow, leading to lots of monadic steps inside an RAction.

To alleviate this somewhat, we use Within b (Path Rel File) as a standard pattern for representing a file within a directory. Within is a type available in the within package that is simply a newtype wrapper over an Env comonad with the environment specialized to Path b Dir. We provide variants of the file operations and rules that typically accept or return Paths or contain callbacks that expect paths and change these to Within values. These functions are generally suffixed within. Here is the variant of getDirectoryFiles that produces Within values.

getDirectoryFilesWithin' :: MonadAction m => Path b Dir -> [FilePattern] -> m [Within b (Path Rel File)]

You can convert to and from this within-style using within and fromWithin.

let x = $(mkRelFile "a.txt") `within` $(mkRelDir "foo") -- Within Rel (Path Rel File)
fromWithin x -- produces a `Path Rel File`

and you can assert that an existing path lies in a directory by using asWithin, which throws if the directory is not a proper prefix of the Path.

$(mkRelFile "foo/a.txt") `asWithin` $(mkRelDir "foo") -- fine
$(mkRelFile "a.txt") `asWithin` $(mkRelDir "foo") -- throws error

Filerules such as (%>) have within-style variants that accept an (Path b Dir) FilePattern on the left and carry that env to the callback.

(%^>) :: (Partial, MonadReader r m, MonadRules m) => Within Rel FilePattern -> (Within Rel (Path Rel File) -> RAction r ()) -> m ()

You change the underlying filepath with fmap or mapM, whilst you can move to a new parent directory by using localDir, or localDirM which is defined in the Within library for when the map between parent directories may throw. The Within library also contains more functions and instances for more precise changes between output and source directories.


The main entry point to this library is the runShakePlus function, which collapses a ReaderT r Rules () to a Rules () and passes the environment to each underlying RAction. The rs in ShakePlus and the underlying RActions have to match. A typical setup might look like this.

let r = --setup env here
shake shakeOptions $ do

    -- include some regular shake rules.

    runShakePlus r $ do

      -- some shake-plus rules.