jfsm 0.1.5

A command-line tool to read file system metadata then return it in JSON format (output and errors).
Documentation
use serde::{Deserialize, Serialize};
use crate::raw::RawMetadata;
use std::path::Path;
use std::env;
// false positive warning for unused imports
#[allow(unused_imports)]
use pathdiff::diff_paths;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FpDirMetadata {
    pub dirn: String,
    pub basen: String,
    pub relp: String,
    pub reldirp: String,
    pub ext: String,
    pub fnnoext: String,
    pub shortp: String,
    pub filet: String,
    pub sizeh: String,
}

impl FpDirMetadata {
    pub fn from_raw(raw: &RawMetadata) -> Self {
        // Use the canonicalized pathname to ensure we have an absolute path
        let path = Path::new(&raw.pathname);
        
        // Get current working directory
        let current_dir = env::current_dir().unwrap_or_else(|_| Path::new(".").to_path_buf());
        
        // Directory name (absolute path without filename)
        let dir_name = path.parent()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_else(|| "/".to_string());
        
        // Base name (file name with extension)
        let base_name = path.file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_default();
        
        // Relative path from current directory
        let rel_path = path.strip_prefix(&current_dir)
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_else(|_| {
                // If strip_prefix fails, we need to compute the relative path manually
                // This happens when the path is not a subpath of current_dir
                pathdiff::diff_paths(&path, &current_dir)
                    .map(|p| p.to_string_lossy().to_string())
                    .unwrap_or_else(|| path.to_string_lossy().to_string())
            });
        
        // Relative folder path from current directory
        let rel_dir_path = path.parent()
            .and_then(|p| p.strip_prefix(&current_dir).ok())
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_else(|| {
                path.parent()
                    .and_then(|p| pathdiff::diff_paths(p, &current_dir))
                    .map(|p| p.to_string_lossy().to_string())
                    .unwrap_or_else(|| {
                        path.parent()
                            .map(|p| p.to_string_lossy().to_string())
                            .unwrap_or_default()
                    })
            });
        
        // File extension
        let extension = path.extension()
            .map(|e| e.to_string_lossy().to_string())
            .unwrap_or_default();
        
        // File name without extension
        let file_name_no_ext = path.file_stem()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or(base_name.clone());
        
        // Short path (parent_directory/filename_with_extension)
        let short_path = if let Some(parent) = path.parent() {
            if let Some(parent_name) = parent.file_name() {
                format!("{}/{}", parent_name.to_string_lossy(), base_name)
            } else {
                base_name.clone()
            }
        } else {
            base_name.clone()
        };
        
        // File type in human-readable format
        let file_type = match raw.ftvalue.as_str() {
            "S_IFDIR" => "directory".to_string(),
            "S_IFREG" => "regular file".to_string(),
            "S_IFLNK" => "symlink".to_string(),
            "S_IFBLK" => "block device".to_string(),
            "S_IFCHR" => "character device".to_string(),
            "S_IFIFO" => "FIFO/pipe".to_string(),
            "S_IFSOCK" => "socket".to_string(),
            _ => "unknown".to_string(),
        };
        
        // Human-readable size
        let size_human = format_size(raw.size);
        
        FpDirMetadata {
            dirn: dir_name,
            basen: base_name,
            relp: rel_path,
            reldirp: rel_dir_path,
            ext: extension,
            fnnoext: file_name_no_ext,
            shortp: short_path,
            filet: file_type,
            sizeh: size_human,
        }
    }
}

// Helper function to format size in human-readable format
fn format_size(size: u64) -> String {
    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
    let mut size = size as f64;
    let mut unit_index = 0;
    
    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
        size /= 1024.0;
        unit_index += 1;
    }
    
    if unit_index == 0 {
        format!("{} {}", size as u64, UNITS[unit_index])
    } else {
        format!("{:.1} {}", size, UNITS[unit_index])
    }
}