feat: cli base
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
1130
Cargo.lock
generated
Normal file
1130
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "fujicli"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
description = "A CLI to manage Fujifilm devices, simulations, backups, and rendering"
|
||||||
|
authors = [
|
||||||
|
"Nikolaos Karaolidis <nick@karaolidis.com>",
|
||||||
|
]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = 'abort'
|
||||||
|
strip = true
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.5.48", features = ["derive", "wrap_help"] }
|
||||||
|
libptp = "0.6.5"
|
||||||
|
log = "0.4.28"
|
||||||
|
log4rs = "1.4.0"
|
||||||
|
rusb = "0.9.4"
|
19
src/cli/backup.rs
Normal file
19
src/cli/backup.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use super::common::file::{Input, Output};
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum BackupCmd {
|
||||||
|
/// Export backup
|
||||||
|
#[command(alias = "e")]
|
||||||
|
Export {
|
||||||
|
/// Output file (use '-' to write to stdout)
|
||||||
|
output_file: Output,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Import backup
|
||||||
|
#[command(alias = "i")]
|
||||||
|
Import {
|
||||||
|
/// Input file (use '-' to read from stdin)
|
||||||
|
input_file: Input,
|
||||||
|
},
|
||||||
|
}
|
35
src/cli/common/file.rs
Normal file
35
src/cli/common/file.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use std::{error::Error, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Input {
|
||||||
|
Path(PathBuf),
|
||||||
|
Stdin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Input {
|
||||||
|
type Err = Box<dyn Error + Send + Sync>;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == "-" {
|
||||||
|
Ok(Input::Stdin)
|
||||||
|
} else {
|
||||||
|
Ok(Input::Path(PathBuf::from(s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Output {
|
||||||
|
Path(PathBuf),
|
||||||
|
Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Output {
|
||||||
|
type Err = Box<dyn Error + Send + Sync>;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == "-" {
|
||||||
|
Ok(Output::Stdout)
|
||||||
|
} else {
|
||||||
|
Ok(Output::Path(PathBuf::from(s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/cli/common/film.rs
Normal file
28
src/cli/common/film.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SimulationSelector {
|
||||||
|
Slot(u8),
|
||||||
|
Name(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for SimulationSelector {
|
||||||
|
type Err = Box<dyn Error + Send + Sync>;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if let Ok(slot) = s.parse::<u8>() {
|
||||||
|
return Ok(SimulationSelector::Slot(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.is_empty() {
|
||||||
|
Err("Simulation name cannot be empty".into())
|
||||||
|
} else {
|
||||||
|
Ok(SimulationSelector::Name(s.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct FilmSimulationOptions {}
|
2
src/cli/common/mod.rs
Normal file
2
src/cli/common/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod file;
|
||||||
|
pub mod film;
|
12
src/cli/device.rs
Normal file
12
src/cli/device.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum DeviceCmd {
|
||||||
|
/// List devices
|
||||||
|
#[command(alias = "l")]
|
||||||
|
List,
|
||||||
|
|
||||||
|
/// Dump device info
|
||||||
|
#[command(alias = "i")]
|
||||||
|
Info,
|
||||||
|
}
|
55
src/cli/mod.rs
Normal file
55
src/cli/mod.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
mod backup;
|
||||||
|
mod common;
|
||||||
|
mod device;
|
||||||
|
mod render;
|
||||||
|
mod simulation;
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
use backup::BackupCmd;
|
||||||
|
use device::DeviceCmd;
|
||||||
|
use render::RenderCmd;
|
||||||
|
use simulation::SimulationCmd;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None, author)]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Subcommands
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Commands,
|
||||||
|
|
||||||
|
/// Format output using json
|
||||||
|
#[arg(long, short = 'j', global = true)]
|
||||||
|
pub json: bool,
|
||||||
|
|
||||||
|
/// Only log warnings and errors
|
||||||
|
#[arg(long, short = 'q', global = true, conflicts_with = "verbose")]
|
||||||
|
pub quiet: bool,
|
||||||
|
|
||||||
|
/// Log extra debugging information
|
||||||
|
#[arg(long, short = 'v', global = true, conflicts_with = "quiet")]
|
||||||
|
pub verbose: bool,
|
||||||
|
|
||||||
|
/// Manually specify target device
|
||||||
|
#[arg(long, short = 'd', global = true)]
|
||||||
|
pub device: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum Commands {
|
||||||
|
/// Manage devices
|
||||||
|
#[command(alias = "d", subcommand)]
|
||||||
|
Device(DeviceCmd),
|
||||||
|
|
||||||
|
/// Manage film simulations
|
||||||
|
#[command(alias = "s", subcommand)]
|
||||||
|
Simulation(SimulationCmd),
|
||||||
|
|
||||||
|
/// Manage backups
|
||||||
|
#[command(alias = "b", subcommand)]
|
||||||
|
Backup(BackupCmd),
|
||||||
|
|
||||||
|
/// Render images
|
||||||
|
#[command(alias = "r")]
|
||||||
|
Render(RenderCmd),
|
||||||
|
}
|
27
src/cli/render.rs
Normal file
27
src/cli/render.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use super::common::{
|
||||||
|
file::{Input, Output},
|
||||||
|
film::{FilmSimulationOptions, SimulationSelector},
|
||||||
|
};
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct RenderCmd {
|
||||||
|
/// Simulation number or name
|
||||||
|
#[arg(long, conflicts_with = "simulation_file")]
|
||||||
|
simulation: Option<SimulationSelector>,
|
||||||
|
|
||||||
|
/// Path to exported simulation
|
||||||
|
#[arg(long, conflicts_with = "simulation")]
|
||||||
|
simulation_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[command(flatten)]
|
||||||
|
film_simulation_options: FilmSimulationOptions,
|
||||||
|
|
||||||
|
/// RAF input file (use '-' to read from stdin)
|
||||||
|
input: Input,
|
||||||
|
|
||||||
|
/// Output file (use '-' to write to stdout)
|
||||||
|
output: Output,
|
||||||
|
}
|
49
src/cli/simulation.rs
Normal file
49
src/cli/simulation.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use super::common::{
|
||||||
|
file::{Input, Output},
|
||||||
|
film::{FilmSimulationOptions, SimulationSelector},
|
||||||
|
};
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum SimulationCmd {
|
||||||
|
/// List simulations
|
||||||
|
#[command(alias = "l")]
|
||||||
|
List,
|
||||||
|
|
||||||
|
/// Get simulation
|
||||||
|
#[command(alias = "g")]
|
||||||
|
Get {
|
||||||
|
/// Simulation number or name
|
||||||
|
simulation: SimulationSelector,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Set simulation parameters
|
||||||
|
#[command(alias = "s")]
|
||||||
|
Set {
|
||||||
|
/// Simulation number or name
|
||||||
|
simulation: SimulationSelector,
|
||||||
|
|
||||||
|
#[command(flatten)]
|
||||||
|
film_simulation_options: FilmSimulationOptions,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Export simulation
|
||||||
|
#[command(alias = "e")]
|
||||||
|
Export {
|
||||||
|
/// Simulation number or name
|
||||||
|
simulation: SimulationSelector,
|
||||||
|
|
||||||
|
/// Output file (use '-' to write to stdout)
|
||||||
|
output_file: Output,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Import simulation
|
||||||
|
#[command(alias = "i")]
|
||||||
|
Import {
|
||||||
|
/// Simulation number
|
||||||
|
slot: u8,
|
||||||
|
|
||||||
|
/// Input file (use '-' to read from stdin)
|
||||||
|
input_file: Input,
|
||||||
|
},
|
||||||
|
}
|
32
src/log.rs
Normal file
32
src/log.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use log::LevelFilter;
|
||||||
|
use log4rs::{
|
||||||
|
Config,
|
||||||
|
append::console::{ConsoleAppender, Target},
|
||||||
|
config::{Appender, Root},
|
||||||
|
encode::pattern::PatternEncoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(quiet: bool, verbose: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let level = if quiet {
|
||||||
|
LevelFilter::Warn
|
||||||
|
} else if verbose {
|
||||||
|
LevelFilter::Debug
|
||||||
|
} else {
|
||||||
|
LevelFilter::Info
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = Box::new(PatternEncoder::new("{d} {h({l})} {M}::{L} - {m}{n}"));
|
||||||
|
|
||||||
|
let console = ConsoleAppender::builder()
|
||||||
|
.encoder(encoder)
|
||||||
|
.target(Target::Stderr)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let config = Config::builder()
|
||||||
|
.appender(Appender::builder().build("stderr", Box::new(console)))
|
||||||
|
.build(Root::builder().appender("stderr").build(level))?;
|
||||||
|
|
||||||
|
log4rs::init_config(config)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
15
src/main.rs
Normal file
15
src/main.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
mod cli;
|
||||||
|
mod log;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let cli = cli::Cli::parse();
|
||||||
|
|
||||||
|
log::init(cli.quiet, cli.verbose)?;
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Reference in New Issue
Block a user