added yaml,json + configurable keys

This commit is contained in:
2026-03-16 17:36:04 +01:00
parent 8cee54007f
commit 253c69363d
12 changed files with 922 additions and 302 deletions

View File

@@ -1,53 +1,93 @@
mod app;
mod cli;
mod config;
mod env;
mod format;
mod runner;
mod ui;
use app::{App, Mode};
use app::App;
use config::load_config;
use env::{merge_env, parse_env_example, write_env};
use format::{detect_format, get_handler};
use runner::AppRunner;
use std::error::Error;
use std::io;
use std::path::{Path, PathBuf};
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
event::{DisableMouseCapture, EnableMouseCapture},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{
Terminal,
backend::{Backend, CrosstermBackend},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
fn determine_output_path(input: &Path) -> PathBuf {
let file_name = input.file_name().unwrap_or_default().to_string_lossy();
if file_name == ".env.example" {
return input.with_file_name(".env");
}
if file_name == "docker-compose.yml" {
return input.with_file_name("docker-compose.override.yml");
}
if file_name == "docker-compose.yaml" {
return input.with_file_name("docker-compose.override.yaml");
}
if file_name.ends_with(".example.json") {
return input.with_file_name(file_name.replace(".example.json", ".json"));
}
if file_name.ends_with(".template.json") {
return input.with_file_name(file_name.replace(".template.json", ".json"));
}
input.with_extension(format!(
"{}.out",
input.extension().unwrap_or_default().to_string_lossy()
))
}
fn main() -> Result<(), Box<dyn Error>> {
let example_path = ".env.example";
let env_path = ".env";
let args = cli::parse();
// Load vars
let mut vars = parse_env_example(example_path).unwrap_or_else(|_| vec![]);
if vars.is_empty() {
println!("No variables found in .env.example or file does not exist.");
println!("Please run this tool in a directory with a valid .env.example file.");
let input_path = args.input;
if !input_path.exists() {
println!("Input file does not exist: {}", input_path.display());
return Ok(());
}
// Merge existing .env if present
let _ = merge_env(env_path, &mut vars);
let format_type = detect_format(&input_path, args.format);
let handler = get_handler(format_type);
let output_path = args
.output
.unwrap_or_else(|| determine_output_path(&input_path));
let mut vars = handler.parse(&input_path).unwrap_or_else(|err| {
println!("Error parsing input file: {}", err);
vec![]
});
if vars.is_empty() {
println!(
"No variables found in {} or file could not be parsed.",
input_path.display()
);
return Ok(());
}
if let Err(e) = handler.merge(&output_path, &mut vars) {
println!("Warning: Could not merge existing output file: {}", e);
}
let config = load_config();
let mut app = App::new(vars);
// Setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// Run app
let res = run_app(&mut terminal, &mut app, &config, env_path);
let mut runner = AppRunner::new(&mut terminal, &mut app, &config, &output_path, handler.as_ref());
let res = runner.run();
// Restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
@@ -62,110 +102,3 @@ fn main() -> Result<(), Box<dyn Error>> {
Ok(())
}
fn run_app<B: Backend>(
terminal: &mut Terminal<B>,
app: &mut App,
config: &config::Config,
env_path: &str,
) -> io::Result<()>
where
io::Error: From<B::Error>,
{
// For handling commands like :w, :q
let mut command_buffer = String::new();
loop {
terminal.draw(|f| ui::draw(f, app, config))?;
if let Event::Key(key) = event::read()? {
match app.mode {
Mode::Normal => {
if !command_buffer.is_empty() {
if key.code == KeyCode::Enter {
match command_buffer.as_str() {
":w" => {
if write_env(env_path, &app.vars).is_ok() {
app.status_message = Some("Saved to .env".to_string());
} else {
app.status_message =
Some("Error saving to .env".to_string());
}
}
":q" => return Ok(()),
":wq" => {
write_env(env_path, &app.vars)?;
return Ok(());
}
_ => {
app.status_message = Some("Unknown command".to_string());
}
}
command_buffer.clear();
} else if key.code == KeyCode::Esc {
command_buffer.clear();
app.status_message = None;
} else if key.code == KeyCode::Backspace {
command_buffer.pop();
if command_buffer.is_empty() {
app.status_message = None;
} else {
app.status_message = Some(command_buffer.clone());
}
} else if let KeyCode::Char(c) = key.code {
command_buffer.push(c);
app.status_message = Some(command_buffer.clone());
}
} else {
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('j') | KeyCode::Down => app.next(),
KeyCode::Char('k') | KeyCode::Up => app.previous(),
KeyCode::Char('i') => {
app.enter_insert();
app.status_message = None;
}
KeyCode::Char(':') => {
command_buffer.push(':');
app.status_message = Some(command_buffer.clone());
}
KeyCode::Enter => {
// Default action for Enter in Normal mode is save
if write_env(env_path, &app.vars).is_ok() {
app.status_message = Some("Saved to .env".to_string());
} else {
app.status_message = Some("Error saving to .env".to_string());
}
}
_ => {}
}
}
}
Mode::Insert => match key.code {
KeyCode::Esc => {
app.enter_normal();
}
KeyCode::Char(c) => {
if let Some(var) = app.vars.get_mut(app.selected) {
var.value.push(c);
}
}
KeyCode::Backspace => {
if let Some(var) = app.vars.get_mut(app.selected) {
var.value.pop();
}
}
KeyCode::Enter => {
app.enter_normal();
}
_ => {}
},
}
}
if !app.running {
break;
}
}
Ok(())
}