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 System.Directory
|
||||||
|
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
|
import qualified Data.Map as M
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
|
||||||
import Plated.Options
|
import Plated.Options
|
||||||
@ -15,13 +16,16 @@ main = do
|
|||||||
envVars <- getEnvVars
|
envVars <- getEnvVars
|
||||||
filenames <- getArgs
|
filenames <- getArgs
|
||||||
templates <- traverse (templateFromFile >=> handleTemplateError) filenames
|
templates <- traverse (templateFromFile >=> handleTemplateError) filenames
|
||||||
runReaderT (renderOutput templates) (Just envVars)
|
runReaderT (renderOutput templates) envVars
|
||||||
where
|
where
|
||||||
renderOutput = traverse_ (interpTemplate >=> liftIO . putStr)
|
renderOutput = traverse_ (interpTemplate >=> liftIO . putStr)
|
||||||
|
|
||||||
getEnvVars :: IO EnvVars
|
getEnvVars :: IO EnvVars
|
||||||
getEnvVars = do
|
getEnvVars = do
|
||||||
cwd <- getCurrentDirectory
|
cwd <- getCurrentDirectory
|
||||||
envVars <- getProjectOptions cwd
|
globalEnvVars <- getEnvironment
|
||||||
envTemplates <- traverse (handleTemplateError . parseTemplate "env.yaml") envVars
|
localEnvVars <- getProjectOptions cwd
|
||||||
runReaderT (traverse interpTemplate envTemplates) mempty
|
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' }}"
|
DATE: "{{ date +'%B %d, %Y' }}"
|
||||||
title: Testing Templating
|
TITLE: Basic Templating
|
||||||
hello: "{{echo $hello}}"
|
|
||||||
|
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
|
name: plated
|
||||||
version: 0.1.0.0
|
version: 0.1.0.0
|
||||||
-- synopsis:
|
synopsis: A dead-simple shell interpolation templating utility
|
||||||
-- description:
|
-- description: A dead-simple shell interpolation templating utility
|
||||||
homepage: https://github.com/ChrisPenner/plated#readme
|
homepage: https://github.com/ChrisPenner/plated#readme
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Chris Penner
|
author: Chris Penner
|
||||||
maintainer: christopher.penner@gmail.com
|
maintainer: christopher.penner@gmail.com
|
||||||
copyright: 2017 Chris Penner
|
copyright: 2017 Chris Penner
|
||||||
category: Web
|
category: Templating
|
||||||
build-type: Simple
|
build-type: Simple
|
||||||
extra-source-files: README.md
|
extra-source-files: README.md
|
||||||
cabal-version: >=1.10
|
cabal-version: >=1.10
|
||||||
@ -18,26 +18,25 @@ library
|
|||||||
exposed-modules: Plated.Template
|
exposed-modules: Plated.Template
|
||||||
, Plated.Parser
|
, Plated.Parser
|
||||||
, Plated.Options
|
, Plated.Options
|
||||||
build-depends: base >= 4.7 && < 5
|
build-depends: base >= 4.9 && < 5
|
||||||
, text
|
, containers
|
||||||
|
, directory
|
||||||
|
, filepath
|
||||||
|
, mtl
|
||||||
, parsec
|
, parsec
|
||||||
, process
|
, process
|
||||||
, yaml
|
, yaml
|
||||||
, containers
|
|
||||||
, filepath
|
|
||||||
, directory
|
|
||||||
, mtl
|
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
executable plated-exe
|
executable plated
|
||||||
hs-source-dirs: app
|
hs-source-dirs: app
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||||
build-depends: base
|
build-depends: base >= 4.9 && < 5
|
||||||
, plated
|
|
||||||
, text
|
|
||||||
, directory
|
, directory
|
||||||
, mtl
|
, mtl
|
||||||
|
, plated
|
||||||
|
, containers
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
test-suite plated-test
|
test-suite plated-test
|
||||||
|
@ -15,16 +15,17 @@ import System.FilePath
|
|||||||
import System.Directory
|
import System.Directory
|
||||||
import System.Exit
|
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
|
getProjectOptions path = do
|
||||||
mProjSettingsFile <- findProjSettings path
|
mProjSettingsFile <- findProjSettings path
|
||||||
mOptions <- traverse optionsFromFilename mProjSettingsFile
|
mOptions <- traverse optionsFromFilename mProjSettingsFile
|
||||||
return $ fromMaybe mempty mOptions
|
return $ fromMaybe mempty mOptions
|
||||||
|
|
||||||
-- Retrieve an options object from a yaml file
|
-- Retrieve an options object from a yaml file
|
||||||
optionsFromFilename :: FilePath -> IO EnvVars
|
optionsFromFilename :: FilePath -> IO EnvMap
|
||||||
optionsFromFilename = Y.decodeFileEither >=>
|
optionsFromFilename = Y.decodeFileEither >=>
|
||||||
\case
|
\case
|
||||||
Left err -> die . prettyPrintParseException $ err
|
Left err -> die . prettyPrintParseException $ err
|
||||||
@ -40,4 +41,4 @@ recurseUp :: FilePath -> [FilePath]
|
|||||||
recurseUp = unfoldr go
|
recurseUp = unfoldr go
|
||||||
where
|
where
|
||||||
go "/" = Nothing
|
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 :: Parser (Template Command)
|
||||||
templateP = "template" ?> do
|
templateP = "template" ?> do
|
||||||
optional shebangP
|
optional (try shebangP)
|
||||||
contents <- many (cmd <|> txt)
|
contents <- many (cmd <|> txt)
|
||||||
eof
|
eof
|
||||||
return $ Template contents
|
return $ Template contents
|
||||||
@ -41,10 +41,11 @@ templateP = "template" ?> do
|
|||||||
|
|
||||||
shebangP :: Parser String
|
shebangP :: Parser String
|
||||||
shebangP = "shebang" ?>
|
shebangP = "shebang" ?>
|
||||||
liftA2 (++) (string "#!") (manyTill anyChar (char '\n'))
|
liftA2 (++) (lookAhead (string "#!") *> string "#!") (manyTill anyChar (char '\n'))
|
||||||
|
|
||||||
commandP :: Parser Command
|
commandP :: Parser Command
|
||||||
commandP = "command" ?> do
|
commandP = "command" ?> do
|
||||||
_ <- string "{{"
|
_ <- string "{{"
|
||||||
cmdString <- manyTill anyChar (string "}}")
|
cmdString <- manyTill anyChar (string "}}")
|
||||||
|
optional newline
|
||||||
return $ Command cmdString
|
return $ Command cmdString
|
||||||
|
@ -10,7 +10,6 @@ import System.Process
|
|||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
import qualified Data.Map as M
|
|
||||||
|
|
||||||
import Plated.Options
|
import Plated.Options
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ data Template a =
|
|||||||
Template [Either String a]
|
Template [Either String a]
|
||||||
deriving Show
|
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
|
interpTemplate (Template elems) = fold <$> mapM toText elems
|
||||||
where
|
where
|
||||||
toText = either return interpCommand
|
toText = either return interpCommand
|
||||||
@ -27,8 +26,8 @@ data Command =
|
|||||||
Command String
|
Command String
|
||||||
deriving Show
|
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
|
interpCommand (Command cmd) = do
|
||||||
envVars <- ask
|
envVars <- ask
|
||||||
let process = (shell cmd){env=M.toList <$> envVars}
|
let process = (shell cmd){env=Just envVars}
|
||||||
liftIO $ readCreateProcess process ""
|
liftIO $ readCreateProcess process ""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user