mirror of
https://github.com/m-lamonaca/json-to-env.git
synced 2025-04-09 12:26:40 +00:00
219 lines
5.7 KiB
Rust
219 lines
5.7 KiB
Rust
use std::{
|
|
fmt::Display,
|
|
fs::File,
|
|
io::{Read, Write},
|
|
};
|
|
|
|
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use serde_json::Value;
|
|
|
|
fn main() -> Result<()> {
|
|
let args = Args::parse();
|
|
|
|
let mut reader: Box<dyn Read> = match args.input {
|
|
Some(ref filename) => Box::new(
|
|
File::open(filename).with_context(|| format!("Could not open file `{filename}`"))?,
|
|
),
|
|
None => Box::new(std::io::stdin()),
|
|
};
|
|
|
|
let mut buffer = String::new();
|
|
let filename = args.input.unwrap_or("STDIN".to_string());
|
|
reader
|
|
.read_to_string(&mut buffer)
|
|
.with_context(|| format!("Could not read `{filename}`"))?;
|
|
|
|
let json: Value = serde_json::from_str(&buffer)
|
|
.with_context(|| format!("`{filename}` does not contain valid JSON"))?;
|
|
|
|
let mut vars: Vec<EnvVar> = vec![];
|
|
let separator = args.separator.unwrap_or("__".to_string());
|
|
JsonParser::parse(&mut vars, "", json, &separator);
|
|
|
|
let environ = vars
|
|
.iter()
|
|
.map(|v| v.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
|
|
let mut writer: Box<dyn Write> = match args.output {
|
|
Some(ref filename) => Box::new(
|
|
File::create(filename).with_context(|| format!("Could not open file `{filename}`"))?,
|
|
),
|
|
None => Box::new(std::io::stdout()),
|
|
};
|
|
|
|
writer
|
|
.write_all(environ.as_bytes())
|
|
.with_context(|| "".to_string())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Parser)]
|
|
#[command(name = "json2env", version, about)]
|
|
struct Args {
|
|
/// Input file, defaults to STDIN if not specified
|
|
#[arg(short, long, value_name = "FILE")]
|
|
input: Option<String>,
|
|
|
|
/// Output file, defaults to STDOUT if not specified
|
|
#[arg(short, long, value_name = "FILE")]
|
|
output: Option<String>,
|
|
|
|
/// Separator for nested keys, defaults to double underscore (__)
|
|
#[arg(short, long, value_name = "STRING")]
|
|
separator: Option<String>,
|
|
}
|
|
|
|
struct JsonParser;
|
|
|
|
impl JsonParser {
|
|
fn parse(lines: &mut Vec<EnvVar>, key: &str, value: Value, separator: &str) {
|
|
match value {
|
|
Value::Array(array) => {
|
|
for (index, item) in array.into_iter().enumerate() {
|
|
let key = Self::build_key(key, index.to_string(), separator);
|
|
Self::parse(lines, key.as_str(), item, separator)
|
|
}
|
|
}
|
|
Value::Object(object) => {
|
|
for (name, value) in object {
|
|
let key = Self::build_key(key, name, separator);
|
|
Self::parse(lines, key.as_str(), value, separator)
|
|
}
|
|
}
|
|
_ => lines.push(EnvVar::new(key.trim().to_owned(), value)),
|
|
}
|
|
}
|
|
|
|
fn build_key(prefix: &str, key: String, separator: &str) -> String {
|
|
match prefix.is_empty() {
|
|
true => key,
|
|
false => format!("{prefix}{separator}{key}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
struct EnvVar {
|
|
name: String,
|
|
value: Value,
|
|
}
|
|
|
|
impl EnvVar {
|
|
fn new(name: String, value: Value) -> Self {
|
|
Self { name, value }
|
|
}
|
|
}
|
|
|
|
impl Display for EnvVar {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let key = self.name.clone();
|
|
match self.value.clone() {
|
|
Value::Null => write!(f, "{key}=null"),
|
|
Value::Bool(bool) => write!(f, "{key}={bool}",),
|
|
Value::Number(number) => write!(f, "{key}={number}"),
|
|
Value::String(string) => write!(f, r#"{key}="{}""#, string.replace('"', r#"\""#)),
|
|
_ => write!(f, ""),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use serde_json::json;
|
|
|
|
use crate::{EnvVar, JsonParser};
|
|
|
|
const KEY: &str = r#""key""#;
|
|
|
|
#[test]
|
|
fn build_key_should_leave_key_unchanged_when_prefix_is_empty() {
|
|
// ARRANGE
|
|
let separator = "";
|
|
let input = KEY.to_owned();
|
|
let expected = KEY;
|
|
|
|
// ACT
|
|
let result = JsonParser::build_key("", input, separator);
|
|
|
|
// ASSERT
|
|
assert_eq!(result, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn build_key_should_leave_prepend_prefix_with_separator() {
|
|
// ARRANGE
|
|
let separator = "_";
|
|
let input = KEY.to_owned();
|
|
let expected = format!("prefix{separator}{KEY}");
|
|
|
|
// ACT
|
|
let actual = JsonParser::build_key("prefix", input, separator);
|
|
|
|
// ASSERT
|
|
assert_eq!(actual, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn bool_env_var_should_be_formatted_correctly() {
|
|
// ARRANGE
|
|
let input = EnvVar::new(KEY.to_owned(), json!(true));
|
|
|
|
// ACT
|
|
let result = input.to_string();
|
|
|
|
// ASSERT
|
|
assert_eq!(result, r#""key"=true"#)
|
|
}
|
|
|
|
#[test]
|
|
fn numeric_env_var_should_be_formatted_correctly() {
|
|
// ARRANGE
|
|
let input = EnvVar::new(KEY.to_owned(), json!(1.0));
|
|
|
|
// ACT
|
|
let result = input.to_string();
|
|
|
|
// ASSERT
|
|
assert_eq!(result, r#""key"=1.0"#)
|
|
}
|
|
|
|
#[test]
|
|
fn string_env_var_should_be_formatted_correctly() {
|
|
// ARRANGE
|
|
let input = EnvVar::new(KEY.to_owned(), json!("hello"));
|
|
|
|
// ACT
|
|
let result = input.to_string();
|
|
|
|
// ASSERT
|
|
assert_eq!(result, r#""key"="hello""#)
|
|
}
|
|
|
|
#[test]
|
|
fn array_env_var_should_be_formatted_correctly() {
|
|
// ARRANGE
|
|
let input = EnvVar::new(KEY.to_owned(), json!([1, 2]));
|
|
|
|
// ACT
|
|
let result = input.to_string();
|
|
|
|
// ASSERT
|
|
assert_eq!(result, "")
|
|
}
|
|
|
|
#[test]
|
|
fn object_env_var_should_be_formatted_correctly() {
|
|
// ARRANGE
|
|
let input = EnvVar::new(KEY.to_owned(), json!({ "key": "value" }));
|
|
|
|
// ACT
|
|
let result = input.to_string();
|
|
|
|
// ASSERT
|
|
assert_eq!(result, "")
|
|
}
|
|
}
|