This commit is contained in:
Chris Penner 2017-04-22 15:36:39 -06:00
parent b5408d90cd
commit e8da25f535
11 changed files with 177 additions and 49 deletions

127
README.md

@ -1 +1,126 @@
# plated
Plated
======
[![Hackage](https://img.shields.io/badge/hackage-latest-green.svg)](https://hackage.haskell.org/package/plated)
A dead-simple templating utility for simple shell interpolation.
Use at your own risk and only on trusted templates.
Here's a simple use-case:
```md
{{ # inside "post.md" }}
# My Blog Post
by {{ echo $AUTHOR }}
Published on {{ date +'%B %d, %Y' }}
Here's my blog post!
---
{{ cat ./footer.md | tr 'a-z' 'A-Z' }}
```
```md
{{ # inside "footer.md" }}
Copyright 2017 Chris Penner
Check me out on twitter @chrislpenner!
See you next time!
```
Then we can interpolate the template:
```
$ plated ./post.md
# My Blog Post
by Chris Penner
Published on April 22, 2017
Here's my blog post!
---
COPYRIGHT 2017 CHRIS PENNER
CHECK ME OUT ON TWITTER @CHRISLPENNER!
SEE YOU NEXT TIME!
```
If you want you can add a shebang to the top of your template and just run it
as an executable, plated will strip the shebang for you automagically:
> test.txt
```
#!/path/to/plated
interpolate {{ echo $THIS }}
```
```bash
$ chmod +x test.txt
$ export THIS="that"
$ ./test.txt
interpolate that
```
[**Examples Here**](https://github.com/ChrisPenner/plated/examples/)
Installation
============
`stack install plated`
FAQ
=====
There's really not much to it; parses the file and runs anything
inside `{{ }}` as a shell expression and pipes stdout into its spot.
If you're clever you can do pretty much anything you want with this.
### Variables?
Sure; It's bash.
```bash
Hello, my name is {{echo $USER}}
```
You can set up environment overrides in `env.yaml`, plated looks up through
the file-system to find an `env.yaml` from the cwd NOT the template location.
Here's an example env.yaml; we can do simple strings or commands here; just make
sure to quote any entries that start with `{{` or the YAML parser gets mad.
> env.yaml
```yaml
PROJECT: Plated
DATE: "{{ date +'%B %d, %Y' }}"
```
Then you can use them just like normal variables.
### For Loops?
It's bash; go for it:
```bash
{{ for i in 99 98 97 ; do
cat <<EOF
$i bottles of beer on the wall
EOF
done }}
```
> output:
```
99 bottles of beer on the wall
98 bottles of beer on the wall
97 bottles of beer on the wall
```
### \_\_: command not found?
Chances are you're forgetting to echo an env-var;
`{{ $TITLE }}` will try to run the contents of `$TITLE` as a command, you want
`{{ echo "$TITLE" }}`.
### Isn't this whole thing a security risk?
Probably.

@ -4,6 +4,7 @@ import System.Environment
import System.Directory
import Data.Foldable
import qualified Data.Map as M
import Control.Monad.Reader
import Plated.Options
@ -15,13 +16,16 @@ main = do
envVars <- getEnvVars
filenames <- getArgs
templates <- traverse (templateFromFile >=> handleTemplateError) filenames
runReaderT (renderOutput templates) (Just envVars)
runReaderT (renderOutput templates) envVars
where
renderOutput = traverse_ (interpTemplate >=> liftIO . putStr)
getEnvVars :: IO EnvVars
getEnvVars = do
cwd <- getCurrentDirectory
envVars <- getProjectOptions cwd
envTemplates <- traverse (handleTemplateError . parseTemplate "env.yaml") envVars
runReaderT (traverse interpTemplate envTemplates) mempty
globalEnvVars <- getEnvironment
localEnvVars <- getProjectOptions cwd
envTemplates <- traverse (handleTemplateError . parseTemplate "env.yaml") localEnvVars
interpolatedLocalEnvVars <- runReaderT (traverse interpTemplate envTemplates) mempty
-- Precedence to local env vars; last in list has precedence
return (globalEnvVars ++ M.toList interpolatedLocalEnvVars)

@ -1,3 +1,2 @@
date: "{{ date +'%B %d, %Y' }}"
title: Testing Templating
hello: "{{echo $hello}}"
DATE: "{{ date +'%B %d, %Y' }}"
TITLE: Basic Templating

3
examples/footer.md Normal file

@ -0,0 +1,3 @@
Copyright 2017 Chris Penner
Check me out on twitter @chrislpenner!
See you next time!

15
examples/simple.md Normal file

@ -0,0 +1,15 @@
{{ # inside "post.md" }}
# {{ echo $TITLE | tr 'a-z' 'A-Z' }}
by {{ echo $USER }}
Published on {{ echo $DATE }}
Here's my blog post!
{{ for i in 99 98 97 ; do
cat <<EOF
$i bottles of beer on the wall
EOF
done }}
---
{{ cat ./footer.md }}

@ -1,4 +0,0 @@
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet.

@ -1,14 +0,0 @@
#!/Users/chris/.local/bin/plated-exe
{{cat | tr 'a-z' 'A-Z' <<EOF
testing
}}
{{ cat ./lorem.md }}
Other text
Title: {{ echo $title }}
Date: {{ echo $date }}
Hello: {{ echo $hello }}
go!

@ -1,14 +1,14 @@
name: plated
version: 0.1.0.0
-- synopsis:
-- description:
synopsis: A dead-simple shell interpolation templating utility
-- description: A dead-simple shell interpolation templating utility
homepage: https://github.com/ChrisPenner/plated#readme
license: BSD3
license-file: LICENSE
author: Chris Penner
maintainer: christopher.penner@gmail.com
copyright: 2017 Chris Penner
category: Web
category: Templating
build-type: Simple
extra-source-files: README.md
cabal-version: >=1.10
@ -18,26 +18,25 @@ library
exposed-modules: Plated.Template
, Plated.Parser
, Plated.Options
build-depends: base >= 4.7 && < 5
, text
build-depends: base >= 4.9 && < 5
, containers
, directory
, filepath
, mtl
, parsec
, process
, yaml
, containers
, filepath
, directory
, mtl
default-language: Haskell2010
executable plated-exe
executable plated
hs-source-dirs: app
main-is: Main.hs
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends: base
, plated
, text
build-depends: base >= 4.9 && < 5
, directory
, mtl
, plated
, containers
default-language: Haskell2010
test-suite plated-test

@ -15,16 +15,17 @@ import System.FilePath
import System.Directory
import System.Exit
type EnvVars = M.Map String String
type EnvVars = [(String, String)]
type EnvMap = M.Map String String
getProjectOptions :: FilePath -> IO EnvVars
getProjectOptions :: FilePath -> IO EnvMap
getProjectOptions path = do
mProjSettingsFile <- findProjSettings path
mOptions <- traverse optionsFromFilename mProjSettingsFile
return $ fromMaybe mempty mOptions
-- Retrieve an options object from a yaml file
optionsFromFilename :: FilePath -> IO EnvVars
optionsFromFilename :: FilePath -> IO EnvMap
optionsFromFilename = Y.decodeFileEither >=>
\case
Left err -> die . prettyPrintParseException $ err
@ -40,4 +41,4 @@ recurseUp :: FilePath -> [FilePath]
recurseUp = unfoldr go
where
go "/" = Nothing
go path = Just (takeDirectory path, takeDirectory path)
go path = Just (path, takeDirectory path)

@ -31,7 +31,7 @@ handleTemplateError (Right temp) = return temp
templateP :: Parser (Template Command)
templateP = "template" ?> do
optional shebangP
optional (try shebangP)
contents <- many (cmd <|> txt)
eof
return $ Template contents
@ -41,10 +41,11 @@ templateP = "template" ?> do
shebangP :: Parser String
shebangP = "shebang" ?>
liftA2 (++) (string "#!") (manyTill anyChar (char '\n'))
liftA2 (++) (lookAhead (string "#!") *> string "#!") (manyTill anyChar (char '\n'))
commandP :: Parser Command
commandP = "command" ?> do
_ <- string "{{"
cmdString <- manyTill anyChar (string "}}")
optional newline
return $ Command cmdString

@ -10,7 +10,6 @@ import System.Process
import Control.Monad.Reader
import Data.Foldable
import qualified Data.Map as M
import Plated.Options
@ -18,7 +17,7 @@ data Template a =
Template [Either String a]
deriving Show
interpTemplate :: (MonadReader (Maybe EnvVars) m, MonadIO m) => Template Command -> m String
interpTemplate :: (MonadReader EnvVars m, MonadIO m) => Template Command -> m String
interpTemplate (Template elems) = fold <$> mapM toText elems
where
toText = either return interpCommand
@ -27,8 +26,8 @@ data Command =
Command String
deriving Show
interpCommand :: (MonadReader (Maybe EnvVars) m, MonadIO m) => Command -> m String
interpCommand :: (MonadReader EnvVars m, MonadIO m) => Command -> m String
interpCommand (Command cmd) = do
envVars <- ask
let process = (shell cmd){env=M.toList <$> envVars}
let process = (shell cmd){env=Just envVars}
liftIO $ readCreateProcess process ""