Skip to content

Commit

Permalink
Change ergonomics of args and add README content
Browse files Browse the repository at this point in the history
  • Loading branch information
lumasepa committed Sep 6, 2019
1 parent e03d82a commit 479e3c5
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 47 deletions.
145 changes: 144 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# j2_render

j2_render is CLI tool to render [jinja 2]() templates, it can load the context from different sources.
j2_render is a static binary CLI tool to render [jinja 2]() templates, it can load the context from different sources.

j2_render is writen in rust and uses [tera]() as jinja 2 engine,
may be some differences between implementations, so look at tera's documentation for reference
Expand All @@ -13,6 +13,149 @@ sudo curl -L "https://github.com/lumasepa/j2_render/releases/download/${J2_RENDE
sudo chmod +x /usr/local/bin/j2_render
```

## Working with the Context

The context in j2_render is a json.
The context can be loaded from different formats and ways.
The context is populated in args order so last arg overwrites keys of previous ones

### Supported Context formats

* json
* yaml
* toml
* hcl/tf/tfvars
* key=value

#### Future Supported
* http/s + json/yaml/toml/hcl/tf/tfvars `format+https?://...`

### Supported Context Inputs

* context file `--file` `-f` `file_path` or `format+file_path`
* environment variables `--env` `-e`
* arguments `--var` `-v` `key=value` or `format+key=value`
* stdin `--stdin` `-i` json/yaml/toml/hcl/tf/tfvars

## Working with templates
Templates are jinja 2 templates implemented by tera, for reference of the jinja 2 template lenguaje go to [tera doc]()

### Supported Templates Inputs

* Template file `--file` `-f` `file_path`
* stdin `--stdin` `-i` `template`

## Usage

```
j2_render [FLAGS]
OPTIONS:
FORMATS = json,yaml,hcl,tfvars,tf,template,j2,tpl
FILE_PATH = file_path.FORMAT or FORMAT+file_path -- path to a file ctx, template or output
VAR = key=value or FORMAT+key=value -- if format provided value will be parsed as format
FLAGS:
--stdin/-i FORMAT -- read from stdin context or template
--out/-o file_path -- output file for rendered template, default stdout
--env/-e -- load env vars in ctx
--file/-f FILE_PATH -- loads a file as context or template depending on extension or format
--var/-v VAR -- adds a pair key value to the context or a template depending on format
--print-ctx/-p -- print the context as json and exits
--help/-h -- shows this help
```

#### Usage Examples

##### Render a template using a context file

```bash
j2_render -f ctx.yaml -f template.j2 > result
```

##### Render a template using environment variables

```bash
j2_render --env -f template.j2 > result
```

##### Render a template using context from stdin

```bash
cat ctx.yaml | j2_render -i yaml -f template.j2 > result
```

#### Render a template using context from var

```bash
echo "Hello {{name}}" | j2_render --var "name=world" -i j2 > result
```

##### Render a template from stdin

```bash
cat template.j2 | j2_render -i j2 -f ctx.yaml > result
```

#### Render a template from vars

```bash
TEMPLATE=$(cat template.j2)
j2_render --var "j2+=${TEMPLATE}" -f ctx.yaml > result
```

#### Render a template using context mixed by different inputs

```bash
cat ctx.json | j2_render --var "name=batman" -i json --env -f ctx.yaml --var "json+list=[1,2,3]" -f template.j2 > result
```

##### Abuse to convert to json

```bash
j2_render -f file.yaml --print-ctx > file.json
```

```bash
j2_render -f file.tfvars --print-ctx > file.json
```

```bash
j2_render -f file.toml --print-ctx > file.json
```

### Extensions to jinja 2

### filters

* "bash"
* "sed"
* "glob"
* "read_file"
* "file_name"
* "file_dir"
* "strip_line_breaks"
* "remove_extension"
* "b64decode"
* "b64encode"
* "str"
* "to_json"
* "from_json"

### functions

* "tab_all_lines"
* "bash"
* "str"
* "to_json"
* "from_json"

### testers

* "file"
* "directory"



126 changes: 80 additions & 46 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,96 +19,130 @@ mod functions;
mod inners;
mod testers;

pub struct Config {
pub template: String,
pub context: Context,
pub out_file: Option<String>,
pub print_ctx: bool,
}

pub fn help() {
println!(
"
j2_render [opts]
--stdin -i [json,yaml,hcl,tfvars,template] read from stdin context or template
--out [file_path] output file for rendered template, default stdout
--env -e load env vars in ctx
--ctx -c [file_path] context files to be loaded in context, multiple files allowed, default empty
--var -v key=value adds a pair key value to the context
--template -t [file_path] template to be rendered, default empty
--print-ctx -p print the context as json and exits
--help shows this help
j2_render [FLAGS]
OPTIONS:
FORMATS = json,yaml,hcl,tfvars,tf,template,j2,tpl
FILE_PATH = file_path.FORMAT or FORMAT+file_path -- path to a file ctx, template or output
VAR = key=value or FORMAT+key=value -- if format provided value will be parsed as format
FLAGS:
--stdin/-i FORMAT -- read from stdin context or template
--out/-o file_path -- output file for rendered template, default stdout
--env/-e -- load env vars in ctx
--file/-f FILE_PATH -- loads a file as context or template depending on extension or format
--var/-v VAR -- adds a pair key value to the context or a template depending on format
--print-ctx/-p -- print the context as json and exits
--help/-h -- shows this help
"
)
}

pub fn parse_args() -> (String, Option<String>, bool, Context) {
fn extract_format(string: &str) -> Option<(String,String)> {
let mut parts : Vec<&str>= string.splitn(2, '+').collect();
let format = parts.pop().expect("");
if parts.len() == 0 {
return None
}
let other = parts.pop().expect("");
return Some((format.to_string(), other.to_string()))
}

pub fn parse_args() -> Config {
let mut args = env::args().collect::<Vec<String>>();
args.reverse();

let mut template = String::new();
let mut context = Context::new();
let mut out = None;
let mut print_ctx = false;
let mut config = Config{
template: String::new(),
context: Context::new(),
out_file: None,
print_ctx: false
};

args.pop(); // binary name

while let Some(arg) = args.pop() {
match arg.as_str() {
"--print-ctx" | "-p" => print_ctx = true,
"--var" | "-v" => {
let variable = args
.pop()
.expect("error specified --var/-v flag but not value provided");
let mut parts : Vec<&str> = variable.split('=').collect();
let mut parts : Vec<&str> = variable.splitn(2, '=').collect();
let key = parts.pop().expect("Error no key=value found");
let value = parts.join("=");
context.insert(&key, &value)
let value = parts.pop().expect("Error no key=value found");

if let Some((format, key)) = extract_format(key) {
process_inputs(&mut config, format, value.to_string());
} else {
config.context.insert(&key, &value)
}
}
"--print-ctx" | "-p" => config.print_ctx = true,
"--out" | "-o" => {
let filepath = args
.pop()
.expect("error specified --out/-o flag but not file path provided");
out = Some(filepath);
config.out_file = Some(filepath);
}
"--stdin" | "-i" => {
let format = args
.pop()
.expect("error specified --stdin/-i flag but not format provided");
let mut data = String::new();
io::stdin().read_to_string(&mut data).expect("Error readinf from stdin");
if format == "template" {
template = data
process_inputs(&mut config, format, data);
}
"--file" | "-f" => {
let path = args
.pop()
.expect("error specified --file/-f flag but not context file path provided");
let (format, path) = if let Some((format, path)) = extract_format(&path) {
(format, path)
} else {
populate_ctx(&mut context, format, data);
}
let extension = Path::new(&path)
.extension()
.and_then(OsStr::to_str)
.expect("Error no extension found in ctx file");
(extension.to_string(), path)
};

let data = fs::read_to_string(&path).expect(&format!("Error reading context file {}", path));
process_inputs(&mut config, format, data);
}
"--env" | "-e" => {
let env_vars = env::vars().collect::<HashMap<String, String>>();
for (k, v) in env_vars.iter() {
context.insert(k, v);
config.context.insert(k, v);
}
}
"--ctx" | "-c" => {
let path = args
.pop()
.expect("error specified --ctx/-c flag but not context file path provided");
let extension = Path::new(&path)
.extension()
.and_then(OsStr::to_str)
.expect("Error no extension found in ctx file");
let data = fs::read_to_string(&path).expect(&format!("Error reading context file {}", path));

populate_ctx(&mut context, extension.to_string(), data);
}
"--template" | "-t" => {
let path = args
.pop()
.expect("error specified --template/-t flag but not template path provided");
template = fs::read_to_string(path).expect("Error reading template")
}
"--help" | "help" | "-h" => {
help();
exit(0);
}
_ => panic!("Error argument {} not recognized", arg),
}
}
return (template, out, print_ctx, context);
return config;
}

pub fn process_inputs(mut config: &mut Config, format: String, data: String) {
if format == "template" || format == "tpl" || format == "j2" {
config.template = data
} else {
populate_ctx(&mut config.context, format, data);
}
}

pub fn populate_ctx(context: &mut Context, format: String, data: String) {
Expand Down Expand Up @@ -151,7 +185,7 @@ pub fn populate_ctx(context: &mut Context, format: String, data: String) {
}

pub fn main() -> std::result::Result<(), String> {
let (template, out, print_ctx, context) = parse_args();
let Config{template, context,print_ctx, out_file } = parse_args();

if print_ctx {
println!("{}", context.as_json().expect("Error encoding ctx as json").to_string());
Expand Down Expand Up @@ -187,7 +221,7 @@ pub fn main() -> std::result::Result<(), String> {

let rendered = tera.render("template", &context).expect("Error rendering template");

if let Some(filepath) = out {
if let Some(filepath) = out_file {
let mut file = fs::File::create(&filepath).expect("Error creating output file");
file.write_all(rendered.as_ref()).expect("Error writing to output file");
} else {
Expand Down

0 comments on commit 479e3c5

Please sign in to comment.