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