Skip to content

Commit b21892b

Browse files
committed
Add CLI args to objdiff-gui (incl. --project-dir/-p)
Resolves #41 Resolves #211
1 parent 247d6da commit b21892b

File tree

5 files changed

+205
-21
lines changed

5 files changed

+205
-21
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

objdiff-gui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ wsl = []
2525

2626
[dependencies]
2727
anyhow = "1.0"
28+
argp = "0.4"
2829
cfg-if = "1.0"
2930
const_format = "0.2"
3031
cwdemangle = "1.0"

objdiff-gui/src/app.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ impl App {
431431
app_path: Option<PathBuf>,
432432
graphics_config: GraphicsConfig,
433433
graphics_config_path: Option<PathBuf>,
434+
project_dir: Option<Utf8PlatformPathBuf>,
434435
) -> Self {
435436
// Load previous app state (if any).
436437
// Note that you must enable the `persistence` feature for this to work.
@@ -440,18 +441,26 @@ impl App {
440441
app.appearance = appearance;
441442
}
442443
if let Some(config) = deserialize_config(storage) {
443-
let mut state = AppState { config, ..Default::default() };
444-
if state.config.project_dir.is_some() {
445-
state.config_change = true;
446-
state.watcher_change = true;
447-
}
448-
if state.config.selected_obj.is_some() {
449-
state.queue_build = true;
450-
}
451-
app.view_state.config_state.queue_check_update = state.config.auto_update_check;
444+
let state = AppState { config, ..Default::default() };
452445
app.state = Arc::new(RwLock::new(state));
453446
}
454447
}
448+
{
449+
let mut state = app.state.write().unwrap();
450+
if let Some(project_dir) = project_dir
451+
&& state.config.project_dir.as_ref().is_none_or(|p| *p != project_dir)
452+
{
453+
state.set_project_dir(project_dir);
454+
}
455+
if state.config.project_dir.is_some() {
456+
state.config_change = true;
457+
state.watcher_change = true;
458+
}
459+
if state.config.selected_obj.is_some() {
460+
state.queue_build = true;
461+
}
462+
app.view_state.config_state.queue_check_update = state.config.auto_update_check;
463+
}
455464
app.appearance.init_fonts(&cc.egui_ctx);
456465
app.appearance.utc_offset = utc_offset;
457466
app.app_path = app_path;

objdiff-gui/src/argp_version.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Originally from https://gist.github.com/suluke/e0c672492126be0a4f3b4f0e1115d77c
2+
//! Extend `argp` to be better integrated with the `cargo` ecosystem
3+
//!
4+
//! For now, this only adds a --version/-V option which causes early-exit.
5+
use std::ffi::OsStr;
6+
7+
use argp::{EarlyExit, FromArgs, TopLevelCommand, parser::ParseGlobalOptions};
8+
9+
struct ArgsOrVersion<T>(T)
10+
where T: FromArgs;
11+
12+
impl<T> TopLevelCommand for ArgsOrVersion<T> where T: FromArgs {}
13+
14+
impl<T> FromArgs for ArgsOrVersion<T>
15+
where T: FromArgs
16+
{
17+
fn _from_args(
18+
command_name: &[&str],
19+
args: &[&OsStr],
20+
parent: Option<&mut dyn ParseGlobalOptions>,
21+
) -> Result<Self, EarlyExit> {
22+
/// Also use argp for catching `--version`-only invocations
23+
#[derive(FromArgs)]
24+
struct Version {
25+
/// Print version information and exit.
26+
#[argp(switch, short = 'V')]
27+
pub version: bool,
28+
}
29+
30+
match Version::from_args(command_name, args) {
31+
Ok(v) => {
32+
if v.version {
33+
println!(
34+
"{} {}",
35+
command_name.first().unwrap_or(&""),
36+
env!("CARGO_PKG_VERSION"),
37+
);
38+
std::process::exit(0);
39+
} else {
40+
// Pass through empty arguments
41+
T::_from_args(command_name, args, parent).map(Self)
42+
}
43+
}
44+
Err(exit) => match exit {
45+
EarlyExit::Help(_help) => {
46+
// TODO: Chain help info from Version
47+
// For now, we just put the switch on T as well
48+
T::from_args(command_name, &["--help"]).map(Self)
49+
}
50+
EarlyExit::Err(_) => T::_from_args(command_name, args, parent).map(Self),
51+
},
52+
}
53+
}
54+
}
55+
56+
/// Create a `FromArgs` type from the current process’s `env::args`.
57+
///
58+
/// This function will exit early from the current process if argument parsing was unsuccessful or if information like `--help` was requested.
59+
/// Error messages will be printed to stderr, and `--help` output to stdout.
60+
pub fn from_env<T>() -> T
61+
where T: TopLevelCommand {
62+
argp::parse_args_or_exit::<ArgsOrVersion<T>>(argp::DEFAULT).0
63+
}

objdiff-gui/src/main.rs

Lines changed: 122 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
mod app;
55
mod app_config;
6+
mod argp_version;
67
mod config;
78
mod fonts;
89
mod hotkeys;
@@ -11,19 +12,83 @@ mod update;
1112
mod views;
1213

1314
use std::{
15+
ffi::OsStr,
16+
fmt::Display,
1417
path::PathBuf,
1518
process::ExitCode,
1619
rc::Rc,
20+
str::FromStr,
1721
sync::{Arc, Mutex},
1822
};
1923

2024
use anyhow::{Result, ensure};
25+
use argp::{FromArgValue, FromArgs};
2126
use cfg_if::cfg_if;
27+
use objdiff_core::config::path::check_path_buf;
2228
use time::UtcOffset;
23-
use tracing_subscriber::EnvFilter;
29+
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
30+
use typed_path::Utf8PlatformPathBuf;
2431

2532
use crate::views::graphics::{GraphicsBackend, GraphicsConfig, load_graphics_config};
2633

34+
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
35+
enum LogLevel {
36+
Error,
37+
Warn,
38+
Info,
39+
Debug,
40+
Trace,
41+
}
42+
43+
impl FromStr for LogLevel {
44+
type Err = ();
45+
46+
fn from_str(s: &str) -> Result<Self, Self::Err> {
47+
Ok(match s {
48+
"error" => Self::Error,
49+
"warn" => Self::Warn,
50+
"info" => Self::Info,
51+
"debug" => Self::Debug,
52+
"trace" => Self::Trace,
53+
_ => return Err(()),
54+
})
55+
}
56+
}
57+
58+
impl Display for LogLevel {
59+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60+
f.write_str(match self {
61+
LogLevel::Error => "error",
62+
LogLevel::Warn => "warn",
63+
LogLevel::Info => "info",
64+
LogLevel::Debug => "debug",
65+
LogLevel::Trace => "trace",
66+
})
67+
}
68+
}
69+
70+
impl FromArgValue for LogLevel {
71+
fn from_arg_value(value: &OsStr) -> Result<Self, String> {
72+
String::from_arg_value(value)
73+
.and_then(|s| Self::from_str(&s).map_err(|_| "Invalid log level".to_string()))
74+
}
75+
}
76+
77+
#[derive(FromArgs, PartialEq, Debug)]
78+
/// A local diffing tool for decompilation projects.
79+
struct TopLevel {
80+
#[argp(option, short = 'L')]
81+
/// Minimum logging level. (Default: info)
82+
/// Possible values: error, warn, info, debug, trace
83+
log_level: Option<LogLevel>,
84+
#[argp(option, short = 'p')]
85+
/// Path to the project directory.
86+
project_dir: Option<PathBuf>,
87+
/// Print version information and exit.
88+
#[argp(switch, short = 'V')]
89+
version: bool,
90+
}
91+
2792
fn load_icon() -> Result<egui::IconData> {
2893
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref());
2994
let mut reader = decoder.read_info()?;
@@ -38,23 +103,63 @@ fn load_icon() -> Result<egui::IconData> {
38103
const APP_NAME: &str = "objdiff";
39104

40105
fn main() -> ExitCode {
41-
// Log to stdout (if you run with `RUST_LOG=debug`).
42-
tracing_subscriber::fmt()
43-
.with_env_filter(
44-
EnvFilter::builder()
45-
// Default to info level
46-
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
47-
.from_env_lossy()
48-
// This module is noisy at info level
49-
.add_directive("wgpu_core::device::resource=warn".parse().unwrap()),
50-
)
51-
.init();
106+
let args: TopLevel = argp_version::from_env();
107+
let builder = tracing_subscriber::fmt();
108+
if let Some(level) = args.log_level {
109+
builder
110+
.with_max_level(match level {
111+
LogLevel::Error => LevelFilter::ERROR,
112+
LogLevel::Warn => LevelFilter::WARN,
113+
LogLevel::Info => LevelFilter::INFO,
114+
LogLevel::Debug => LevelFilter::DEBUG,
115+
LogLevel::Trace => LevelFilter::TRACE,
116+
})
117+
.init();
118+
} else {
119+
builder
120+
.with_env_filter(
121+
EnvFilter::builder()
122+
// Default to info level
123+
.with_default_directive(LevelFilter::INFO.into())
124+
.from_env_lossy()
125+
// This module is noisy at info level
126+
.add_directive("wgpu_core::device::resource=warn".parse().unwrap()),
127+
)
128+
.init();
129+
}
52130

53131
// Because localtime_r is unsound in multithreaded apps,
54132
// we must call this before initializing eframe.
55133
// https://github.com/time-rs/time/issues/293
56134
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
57135

136+
// Resolve project directory if provided
137+
let project_dir = if let Some(path) = args.project_dir {
138+
match path.canonicalize() {
139+
Ok(path) => {
140+
// Ensure the path is a directory
141+
if path.is_dir() {
142+
match check_path_buf(path) {
143+
Ok(path) => Some(path),
144+
Err(e) => {
145+
log::error!("Failed to convert project directory to UTF-8 path: {}", e);
146+
None
147+
}
148+
}
149+
} else {
150+
log::error!("Project directory is not a directory: {}", path.display());
151+
None
152+
}
153+
}
154+
Err(e) => {
155+
log::error!("Failed to canonicalize project directory: {}", e);
156+
None
157+
}
158+
}
159+
} else {
160+
None
161+
};
162+
58163
let app_path = std::env::current_exe().ok();
59164
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
60165
let mut native_options = eframe::NativeOptions {
@@ -113,6 +218,7 @@ fn main() -> ExitCode {
113218
app_path.clone(),
114219
graphics_config.clone(),
115220
graphics_config_path.clone(),
221+
project_dir.clone(),
116222
) {
117223
eframe_error = Some(e);
118224
}
@@ -139,6 +245,7 @@ fn main() -> ExitCode {
139245
app_path.clone(),
140246
graphics_config.clone(),
141247
graphics_config_path.clone(),
248+
project_dir.clone(),
142249
) {
143250
eframe_error = Some(e);
144251
} else {
@@ -161,6 +268,7 @@ fn main() -> ExitCode {
161268
app_path,
162269
graphics_config,
163270
graphics_config_path,
271+
project_dir,
164272
) {
165273
eframe_error = Some(e);
166274
} else {
@@ -204,6 +312,7 @@ fn run_eframe(
204312
app_path: Option<PathBuf>,
205313
graphics_config: GraphicsConfig,
206314
graphics_config_path: Option<PathBuf>,
315+
project_dir: Option<Utf8PlatformPathBuf>,
207316
) -> Result<(), eframe::Error> {
208317
eframe::run_native(
209318
APP_NAME,
@@ -216,6 +325,7 @@ fn run_eframe(
216325
app_path,
217326
graphics_config,
218327
graphics_config_path,
328+
project_dir,
219329
)))
220330
}),
221331
)

0 commit comments

Comments
 (0)