SHIPIT!
This commit is contained in:
parent
b5408d90cd
commit
e8da25f535
127
README.md
127
README.md
@ -1 +1,126 @@
|
||||
# plated
|
||||
Plated
|
||||
======
|
||||
|
||||
[](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.
|
||||
|
12
app/Main.hs
12
app/Main.hs
@ -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
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
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!
|
25
plated.cabal
25
plated.cabal
@ -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 ""
|
||||
|
Loading…
x
Reference in New Issue
Block a user