thought 2.0.0

Save your thoughts for later.
Documentation
mod cli;
pub(crate) use cli::get_cli;

mod editor_wrapper;
pub(crate) use editor_wrapper::EditorWrapper;

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

mod state;
pub(crate) use state::State;

mod thought;
pub(crate) use thought::Thought;

mod thought_collection;
pub(crate) use thought_collection::ThoughtCollection;

mod thought_id;
pub(crate) use thought_id::ThoughtId;

mod truncate;
pub(crate) use truncate::Truncate;

mod words;
pub(crate) use words::WORDS;

pub fn run() -> Result<(), Error> {
    let base_dirs = xdg::BaseDirectories::with_prefix("thought")?;

    let thoughts_dir_path = base_dirs.get_data_home().join("thoughts");
    let thought_collection = ThoughtCollection::new(&thoughts_dir_path)?;

    let state_file_path = base_dirs.place_data_file("state.toml").unwrap();
    let raw_state = std::fs::read_to_string(&state_file_path).unwrap_or("".to_string());
    let mut state: State = toml::from_str(&raw_state).unwrap();

    match get_cli().get_matches().subcommand() {
        Some(("new", _)) => {
            let thought = Thought::new(&thoughts_dir_path)?;
            thought.edit(EditorWrapper)?;
            state.last_accessed_thought_id = Some(thought.id().to_string());
            println!("Created note '{}'.", thought.id());
        }
        Some(("list", _)) => {
            for thought in thought_collection.thoughts() {
                println!("{}", thought);
            }
        }
        Some(("edit", matches)) => {
            let raw_thought_id = if matches.get_flag("last") {
                state.last_accessed_thought_id.unwrap()
            } else {
                matches
                    .get_one::<String>("id")
                    .expect("id is required")
                    .to_string()
            };
            let thought_id = raw_thought_id.parse()?;

            let thought = thought_collection.find(&thought_id)?;
            thought.edit(EditorWrapper)?;

            state.last_accessed_thought_id = Some(thought.id().to_string());
            println!("Edited note '{}'.", thought.id());
        }
        Some(("delete", matches)) => {
            let raw_thought_ids = if matches.get_flag("last") {
                vec![state.last_accessed_thought_id.clone().unwrap()]
            } else {
                matches
                    .get_many::<String>("ids")
                    .expect("id is required")
                    .map(ToString::to_string)
                    .collect()
            };

            for raw_thought_id in raw_thought_ids {
                let thought_id: ThoughtId = match raw_thought_id.parse() {
                    Ok(thought_id) => thought_id,
                    Err(e) => {
                        eprintln!("{}", e);
                        continue;
                    }
                };

                let thought = thought_collection.find(&thought_id)?;
                thought.delete()?;

                if let Some(ref last_accessed_thought_id) = state.last_accessed_thought_id {
                    if last_accessed_thought_id == &thought.id().to_string() {
                        state.last_accessed_thought_id = None;
                    }
                }

                println!("Deleted note '{}'.", thought.id());
            }
        }
        Some(("search", matches)) => {
            let pattern = matches
                .get_one::<String>("pattern")
                .expect("pattern is required");

            let mut matching_thoughts = vec![];

            for thought in thought_collection.thoughts() {
                let thought_content = thought.get_content()?;
                if thought_content.to_lowercase().contains(pattern) {
                    matching_thoughts.push(thought.clone());
                }
            }

            for thought in matching_thoughts {
                println!("{}", thought);
            }
        }
        _ => unreachable!(),
    }

    std::fs::write(&state_file_path, toml::to_string(&state).unwrap()).unwrap();

    Ok(())
}