paperslip 0.2.0

Quickly open a note file for your projects.
Documentation
use std::path::{Path, PathBuf};

use tracing::{error, info};

mod cli;

mod error;
pub(crate) use error::Error;

fn get_working_directory() -> Result<PathBuf, Error> {
    std::env::current_dir().map_err(|_| Error::new("failed to get the working directory"))
}

fn get_project_dir(always_use_cwd: bool) -> Result<PathBuf, Error> {
    if always_use_cwd {
        return get_working_directory();
    }

    match std::process::Command::new("git")
        .arg("rev-parse")
        .arg("--show-toplevel")
        .output()
    {
        Ok(output) if output.stderr.is_empty() => {
            Ok(Path::new(String::from_utf8(output.stdout).unwrap().trim()).to_owned())
        }
        Ok(_) => get_working_directory(),
        _ => Err(Error::new("failed to get project root")),
    }
}

fn get_data_dir() -> Result<PathBuf, Error> {
    let app_name = env!("CARGO_PKG_NAME");
    let xdg_dirs = xdg::BaseDirectories::with_prefix(app_name)
        .map_err(|_| Error::new("failed to get data directory for program"))?;
    Ok(xdg_dirs.get_data_home())
}

pub fn run() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::builder()
                .with_default_directive(tracing_subscriber::filter::LevelFilter::OFF.into())
                .from_env_lossy(),
        )
        .init();

    let matches = cli::get_cli().get_matches();

    let data_dir = get_data_dir()?;
    info!("data dir: {:?}", data_dir);

    let project_dir = if matches.contains_id("directory") {
        Path::new(
            matches
                .get_one::<String>("directory")
                .expect("guarded behind matches.contains_id(..)"),
        )
        .to_owned()
    } else {
        let always_use_cwd = matches
            .get_one::<bool>("cwd")
            .expect("should be false if it's not given");
        get_project_dir(*always_use_cwd)?
    };
    let project_dir = project_dir.strip_prefix("/").unwrap();

    info!("project dir: {:?}", project_dir);

    let notes_dir_path = data_dir.join(project_dir);
    info!("notes dir: {:?}", notes_dir_path);

    std::fs::create_dir_all(&notes_dir_path)
        .map_err(|_| Error::new(format!("failed to create the directory {:?}", project_dir)))?;

    let note_file_path = notes_dir_path.join("notes.md");
    info!("note file path: {:?}", note_file_path);

    if !std::path::Path::exists(&note_file_path) {
        match std::fs::File::create(&note_file_path) {
            Ok(_) => {}
            Err(e) => {
                error!("std::fs::File::create failed with '{}'", e);
                return Err(Error::new("failed to create the note file"));
            }
        }
    }

    let editor_command = std::env::var("EDITOR").unwrap_or("xdg-open".to_string());
    info!("editor command: {:?}", editor_command);

    let _ = std::process::Command::new(editor_command)
        .arg(note_file_path)
        .status()
        .map_err(|e| Error::new(format!("failed to start editor: {}", e)))?;

    Ok(())
}