# Shake+ Extended - Experimental Mechanisms For Shake
This library extends
[shake-plus](https://hackage.haskell.org/package/shake-plus), which enriches
[shake](https://hackage.haskell.org/package/shake) with `ReaderT` and the
`Path` library. This extended ruleset introduces
[within](https://hackage.haskell.org/package/within), for better keeping track
of source and output directories, as well as batch loading mechanisms using
[ixset-typed](https://hackage.haskell.org/package/ixset-typed).
## Using Within
One common complaint about Shake is having to keep track of source and output
directories and translating `FilePath`s 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](https://hackage.haskell.org/package/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 `Path`s 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.
```{.haskell}
getDirectoryFilesWithin' :: MonadAction m => Within Rel [FilePattern] -> m [Within b (Path Rel File)]
```
You can convert to and from this within-style using `within` and `fromWithin`.
```{.haskell}
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`.
```{.haskell}
$(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.
```{.haskell}
(%^>) :: (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.