···2233use crate::config::{get_config_dir, get_data_dir};
4455+mod process;
66+57#[derive(Parser, Debug)]
68#[command(author, version = version(), about)]
79pub struct Cli {
···19212022#[derive(Subcommand, Debug)]
2123pub enum Commands {
2222- /// Manage TARS groups.
2323- // #[command(subcommand)]
2424- // Group(GroupSubcommand),
2424+ // / Manage TARS groups.
2525+ // #[command(subcommand)]
2626+ // Group(GroupSubcommand),
25272626- /// Manage TARS tasks.
2727- // #[command(subcommand)]
2828- // Task(TaskSubcommand),
2929-3030- /// simple testing stuff
3131- Test,
3232- // Imports bulk data into TARS
2828+ // / Manage TARS tasks.
2929+ // #[command(subcommand)]
3030+ // Task(TaskSubcommand),
3131+ //
3232+ //
3333+ /// Initalize Filaments.
3434+ ///
3535+ /// This will write a default config to ~/.config/filaments,
3636+ /// as well as creating a new "notebook" in the current
3737+ /// directory with the specified name. Note that we currently
3838+ /// only support one notebook.
3939+ Init {
4040+ #[arg(default_value = "ZettelKasten")]
4141+ name: String,
4242+ },
3343 // NOTE: By default the importer will fill in fields with
3444 // default values if they arent present / aren't able to be
3545 // parsed properly
+54
src/cli/process.rs
···11+use std::{
22+ env::current_dir,
33+ fs::{File, create_dir_all},
44+ io::Write,
55+};
66+77+use color_eyre::eyre::{Context, Result};
88+99+use crate::{
1010+ cli::Commands,
1111+ config::{Config, get_config_dir},
1212+};
1313+1414+impl Commands {
1515+ pub fn process(self) -> Result<()> {
1616+ match self {
1717+ Self::Init { name } => {
1818+ // create the directory
1919+ let dir = current_dir()
2020+ .context("Failed to get current directory")?
2121+ .join(&name);
2222+2323+ // create the .filaments folder
2424+ let filaments_dir = dir.join(".filaments");
2525+2626+ create_dir_all(&filaments_dir)
2727+ .context("Failed to create the filaments directory!")?;
2828+2929+ // create the database inside there
3030+ File::create(filaments_dir.join("filaments.db"))?;
3131+3232+ // write config that sets the filaments directory to current dir!
3333+ let config_kdl = dbg! {Config::generate(&dir)};
3434+3535+ // create the config dir
3636+ let config_dir = get_config_dir();
3737+3838+ create_dir_all(config_dir).expect("creating the config dir should not error");
3939+4040+ let mut config_file = File::create(get_config_dir().join("config.kdl"))
4141+ .context("Failed to create config file")?;
4242+4343+ write!(config_file, "{config_kdl}")?;
4444+4545+ println!("wrote config to {config_file:#?}");
4646+4747+ // report status!
4848+ println!("Initialized successfully!");
4949+ }
5050+ }
5151+5252+ Ok(())
5353+ }
5454+}
+76-17
src/config.rs
···11+use color_eyre::eyre::Context;
12use directories::ProjectDirs;
23use kdl::KdlDocument;
34use serde::Deserialize;
44-use std::{env, path::PathBuf, sync::LazyLock};
55+use std::{
66+ env::{self, home_dir},
77+ fmt::Debug,
88+ fs::File,
99+ io::Read,
1010+ path::{Path, PathBuf},
1111+ sync::LazyLock,
1212+};
513614use crate::keymap::KeyMap;
715···2937#[derive(Clone, Debug, Deserialize, Default)]
3038#[expect(dead_code)]
3139pub struct AppConfig {
4040+ /// The directory where the single instance of the filaments exists.
4141+ pub filaments: PathBuf,
3242 #[serde(default)]
3333- pub data_dir: PathBuf,
4343+ pub data: PathBuf,
3444 #[serde(default)]
3535- pub config_dir: PathBuf,
4545+ pub config: PathBuf,
3646}
37473848/// Configuration for the App
···4555}
46564757impl Config {
4848- pub fn new() -> Self {
4949- let default_config: KdlDocument = DEFAULT_CONFIG
5858+ /// generates a new config with the provided `filaments_dir`
5959+ pub fn generate(filaments_dir: &Path) -> KdlDocument {
6060+ let mut default_config: KdlDocument = DEFAULT_CONFIG
5061 .parse()
5162 .expect("Default config should always be a valid KDL document.");
52635353- let keymap_node = default_config
5454- .get("keymap")
5555- .expect("Config::new Keymap must exist in default config.");
6464+ if let Some(node) = default_config
6565+ .nodes_mut()
6666+ .iter_mut()
6767+ .find(|n| n.name().value() == "filaments_dir")
6868+ && let Some(entry) = node.entries_mut().get_mut(0)
6969+ {
7070+ *entry.value_mut() = kdl::KdlValue::String(filaments_dir.to_string_lossy().to_string());
7171+ entry.clear_format();
7272+ }
7373+7474+ default_config
7575+ }
7676+7777+ /// Parse the config from `~/.config/filametns`
7878+ ///
7979+ /// # Errors
8080+ ///
8181+ /// Will error if the config doesn't exist or if there
8282+ /// is a problem parsing it.
8383+ pub fn parse() -> color_eyre::Result<Self> {
8484+ let config: KdlDocument = {
8585+ let file_path = get_config_dir().join("config.kdl");
8686+8787+ let mut file = File::open(file_path).context("Failed to find file!")?;
8888+8989+ let mut str = String::new();
9090+9191+ file.read_to_string(&mut str)
9292+ .context("Failed to read file!")?;
56935757- let keymap =
5858- KeyMap::try_from(keymap_node).expect("default config should always be a valid keymap");
9494+ str.parse().context("Expected to be valid kdl")?
9595+ };
9696+9797+ let keymap = KeyMap::try_from(
9898+ config
9999+ .get("keymap")
100100+ .expect("Keymap must exist in the config"),
101101+ )
102102+ .context("Keymap is not valid!")?;
591036060- Self {
104104+ let filaments_dir_str = config
105105+ .get("filaments_dir")
106106+ .expect("config should always have this specified")
107107+ .get(0)
108108+ .and_then(|arg| arg.as_string())
109109+ .expect("filaments_dir must be a string");
110110+111111+ let filaments_dir = PathBuf::from(filaments_dir_str)
112112+ .canonicalize()
113113+ .context("Filaments directory does not exist!")?;
114114+115115+ Ok(Self {
61116 app_config: AppConfig {
6262- data_dir: get_data_dir(),
6363- config_dir: get_config_dir(),
117117+ filaments: filaments_dir,
118118+ data: get_data_dir(),
119119+ config: get_config_dir(),
64120 },
65121 keymap,
6666- }
122122+ })
67123 }
68124}
69125···80136/// Returns the path to the OS-agnostic config directory.
81137pub fn get_config_dir() -> PathBuf {
82138 CONFIG_DIRECTORY.clone().unwrap_or_else(|| {
8383- project_directory().map_or_else(
139139+ home_dir().map_or_else(
84140 || PathBuf::from(".").join(".config"),
8585- |proj_dirs| proj_dirs.config_local_dir().to_path_buf(),
141141+ |mut path| {
142142+ path.push(".config");
143143+ path.push("filaments");
144144+ path
145145+ },
86146 )
87147 })
88148}
8989-90149fn project_directory() -> Option<ProjectDirs> {
91150 ProjectDirs::from("com", "suri-codes", env!("CARGO_PKG_NAME"))
92151}
+7-11
src/main.rs
···4455use crate::{app::App, cli::Cli};
66use clap::Parser;
77-use db::Db;
8798mod app;
109mod cli;
···23222423 let args = Cli::parse();
25242626- let _db = Db::connect("/tmp/filaments/test_db.sqlite").await?;
2727-2825 // if there is any subcommand, we want to execute that, otherwise we
2926 // just run the app
2727+ if let Some(command) = args.command {
2828+ return command.process();
2929+ }
30303131- if let Some(command) = args.command {
3232- match command {
3333- cli::Commands::Test => {}
3434- }
3535- } else {
3636- let mut app = App::new(args.tick_rate, args.frame_rate);
3131+ // if no command we run the app
3232+3333+ let mut app = App::new(args.tick_rate, args.frame_rate)?;
3434+ app.run().await?;
37353838- app.run().await?;
3939- }
4036 Ok(())
4137}