psyk/
cli.rs

1// SPDX-FileCopyrightText: © 2025 TTKB, LLC
2// SPDX-License-Identifier: BSD-3-CLAUSE
3
4use std::collections::{HashMap, HashSet};
5use std::env;
6use std::fs::{File, FileTimes};
7use std::io::Write;
8use std::path::{Path, PathBuf};
9
10use anyhow::Result;
11use clap::crate_version;
12
13use super::display;
14use super::io::{read, read_lib, write_lib, write_obj};
15use super::{Module, LIB};
16
17/// Prints information about an [OBJ](super::OBJ) or [LIB].
18pub fn info(
19    write: &mut impl Write,
20    lib_or_obj: &Path,
21    code: bool,
22    disassembly: bool,
23) -> Result<()> {
24    let o = read(lib_or_obj)?;
25    let mut options = display::Options::default();
26    if disassembly {
27        options.code_format = display::CodeFormat::Disassembly;
28    } else if code {
29        options.code_format = display::CodeFormat::Hex;
30    }
31    writeln!(write, "{}", display::PsyXDisplayable::wrap(&o, options))?;
32    Ok(())
33}
34
35pub fn split(lib_path: &Path) -> Result<()> {
36    let lib = read_lib(lib_path)?;
37    println!("psyk version {}\n", crate_version!());
38    for module in lib.modules() {
39        let object_filename = format!("{}.OBJ", module.name());
40        let time = module.created_at().expect("created timestamp");
41        let mut file = File::create(&object_filename)?;
42        let times = FileTimes::new().set_accessed(time).set_modified(time);
43        file.set_times(times)?;
44        write_obj(module.object(), &mut file)?;
45
46        println!("Extracted object file {}", object_filename);
47    }
48    Ok(())
49}
50
51pub fn delete(lib_path: &Path, obj_names: Vec<String>) -> Result<()> {
52    let lib = read_lib(lib_path)?;
53
54    let module_names: HashSet<String> = HashSet::from_iter(obj_names);
55
56    let new_modules: Vec<Module> = lib
57        .modules()
58        .iter()
59        .filter(|m| !module_names.contains(&m.name()))
60        .cloned()
61        .collect::<Vec<Module>>();
62    let lib = LIB::new(new_modules);
63
64    let mut file = File::create(lib_path)?;
65    write_lib(&lib, &mut file)
66}
67
68pub fn join(lib_path: &Path, obj_paths: Vec<PathBuf>) -> Result<()> {
69    let modules = obj_paths
70        .iter()
71        .map(|path| Module::new_from_path(path).expect("module"))
72        .collect::<Vec<Module>>();
73
74    let lib = LIB::new(modules);
75
76    let mut file = File::create(lib_path)?;
77    write_lib(&lib, &mut file)
78}
79
80pub fn add(lib_path: &Path, obj_path: &Path) -> Result<()> {
81    let lib = read_lib(lib_path)?;
82
83    let module = Module::new_from_path(obj_path)?;
84    let mut modules: Vec<Module> = lib.modules().clone();
85    modules.push(module);
86
87    let lib = LIB::new(modules);
88
89    let mut file = File::create(lib_path)?;
90    write_lib(&lib, &mut file)
91}
92
93pub fn update(lib_path: &Path, obj_paths: Vec<PathBuf>) -> Result<()> {
94    let lib = read_lib(lib_path)?;
95
96    let mut updated_module_paths: HashMap<String, PathBuf> = HashMap::new();
97    for path in obj_paths {
98        let module_name = String::from(path.file_stem().expect("file").to_string_lossy());
99        updated_module_paths.insert(module_name, path);
100    }
101
102    let new_modules = lib
103        .modules()
104        .iter()
105        .map({
106            |m| {
107                if let Some(module_path) = updated_module_paths.get(&m.name()) {
108                    let Ok(new_mod) = Module::new_from_path(module_path) else {
109                        eprintln!("could not read: {module_path:?}. Skipping.");
110                        return m.clone();
111                    };
112                    new_mod
113                } else {
114                    m.clone()
115                }
116            }
117        })
118        .collect::<Vec<Module>>();
119    let lib = LIB::new(new_modules);
120
121    let mut file = File::create(lib_path)?;
122    write_lib(&lib, &mut file)
123}
124
125fn stem_or_psyk(path: Option<String>) -> String {
126    path.and_then(|path| {
127        Path::new(&path)
128            .file_stem()
129            .and_then(|s| s.to_str())
130            .map(|s| s.to_lowercase())
131    })
132    .unwrap_or_else(|| "psyk".to_string())
133}
134
135/// Get the binary name from the executable path
136pub fn get_binary_name() -> String {
137    stem_or_psyk(env::args().next())
138}
139
140#[cfg(test)]
141mod test {
142    use super::*;
143
144    #[test]
145    fn test_bin_name() {
146        assert_eq!("psyk", stem_or_psyk(None));
147        assert_eq!("foo", stem_or_psyk(Some("/bin/foo".into())));
148    }
149}