From 9fdc3bc320a24b6f53a8d89174d7f70305b557a5 Mon Sep 17 00:00:00 2001 From: Michael Baudler Date: Thu, 22 Jan 2026 12:20:19 +0100 Subject: [PATCH 1/4] refactor(errors): Moves all errors to a separate crate This makes it easier to link to errors and their explenations via miette. It also allows to share errors between crates. --- Cargo.lock | 12 ++- Cargo.toml | 1 + crates/flow-cli/Cargo.toml | 2 +- crates/flow-cli/src/commands/init.rs | 7 +- crates/flow-cli/src/common.rs | 5 +- crates/flow-cli/src/errors.rs | 21 ----- crates/flow-cli/src/lib.rs | 1 - crates/flow-core/Cargo.toml | 2 +- crates/flow-core/src/config/default.rs | 10 +- crates/flow-core/src/filesystem/local.rs | 22 ++--- crates/flow-core/src/lib.rs | 7 +- crates/flow-core/src/space/default.rs | 8 +- crates/flow-errors/Cargo.toml | 20 ++++ crates/flow-errors/src/cli.rs | 45 +++++++++ crates/flow-errors/src/io.rs | 28 ++++++ crates/flow-errors/src/lib.rs | 39 ++++++++ .../errors.rs => flow-errors/src/space.rs} | 92 +++++-------------- 17 files changed, 197 insertions(+), 125 deletions(-) delete mode 100644 crates/flow-cli/src/errors.rs create mode 100644 crates/flow-errors/Cargo.toml create mode 100644 crates/flow-errors/src/cli.rs create mode 100644 crates/flow-errors/src/io.rs create mode 100644 crates/flow-errors/src/lib.rs rename crates/{flow-core/src/errors.rs => flow-errors/src/space.rs} (66%) diff --git a/Cargo.lock b/Cargo.lock index f6bb64a..912e0f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,11 +599,11 @@ dependencies = [ "clap", "console", "flow-core", + "flow-errors", "inquire", "miette", "serde", "serde_json", - "thiserror 2.0.17", "tokio", ] @@ -612,14 +612,22 @@ name = "flow-core" version = "0.1.0" dependencies = [ "cross-xdg", + "flow-errors", "loro", "miette", "serde", "serde_json", - "thiserror 2.0.17", "tokio", ] +[[package]] +name = "flow-errors" +version = "0.1.0" +dependencies = [ + "miette", + "thiserror 2.0.17", +] + [[package]] name = "flow-gui" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0d3af34..7a1a264 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ rust-version = "1.91.1" [workspace.dependencies] flow-core = { path = "crates/flow-core" } flow-cli = { path = "crates/flow-cli" } +flow-errors = { path = "crates/flow-errors" } flow-app = { path = "crates/flow-app" } flow-tui = { path = "crates/flow-tui" } flow-gui = { path = "crates/flow-gui" } diff --git a/crates/flow-cli/Cargo.toml b/crates/flow-cli/Cargo.toml index 33e0551..a8b121c 100644 --- a/crates/flow-cli/Cargo.toml +++ b/crates/flow-cli/Cargo.toml @@ -14,10 +14,10 @@ rust-version.workspace = true [dependencies] flow-core = { workspace = true } +flow-errors = { workspace = true } clap = { workspace = true } tokio = { workspace = true } miette = { workspace = true } -thiserror = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } inquire = { version = "0.9.2", features = ["date", "editor"] } diff --git a/crates/flow-cli/src/commands/init.rs b/crates/flow-cli/src/commands/init.rs index e966ad3..3c8720c 100644 --- a/crates/flow-cli/src/commands/init.rs +++ b/crates/flow-cli/src/commands/init.rs @@ -6,7 +6,8 @@ use inquire::Text; use miette::IntoDiagnostic; use serde::Serialize; -use crate::{commands::Command, common::OutputArgs, errors::Error, extensions::PathExt}; +use crate::{commands::Command, common::OutputArgs, extensions::PathExt}; +use flow_errors::CliError; #[derive(Args, Debug, Clone)] pub struct Arguments { @@ -86,7 +87,7 @@ impl Command for Init { .args .path .take() - .ok_or_else(|| Error::MissingArgument("path".to_string()))?; + .ok_or_else(|| CliError::MissingArgument("path".to_string()))?; let path_name = path.file_name().and_then(|n| n.to_str()); let name = self @@ -94,7 +95,7 @@ impl Command for Init { .name .take() .or_else(|| path_name.map(String::from)) - .ok_or_else(|| Error::MissingArgument("name".to_string()))?; + .ok_or_else(|| CliError::MissingArgument("name".to_string()))?; let space = Space::init(&path, &name).await?; if !self.args.no_register { diff --git a/crates/flow-cli/src/common.rs b/crates/flow-cli/src/common.rs index b9b19f8..22051e1 100644 --- a/crates/flow-cli/src/common.rs +++ b/crates/flow-cli/src/common.rs @@ -3,7 +3,8 @@ use flow_core::{Config, Space}; use miette::Result; use std::path::PathBuf; -use crate::{errors::Error, printer::Printer}; +use crate::printer::Printer; +use flow_errors::CliError; /// Output formatting arguments - available for ALL commands. #[derive(Args, Debug, Clone)] @@ -74,7 +75,7 @@ impl SpaceArgs { .as_ref() .and_then(|n| config.find(n)) .or_else(|| config.active()) - .ok_or(Error::NoActiveSpace)?; + .ok_or(CliError::NoActiveSpace)?; Space::load(&space.name).await } diff --git a/crates/flow-cli/src/errors.rs b/crates/flow-cli/src/errors.rs deleted file mode 100644 index ffde8ea..0000000 --- a/crates/flow-cli/src/errors.rs +++ /dev/null @@ -1,21 +0,0 @@ -use miette::Diagnostic; -use thiserror::Error; - -#[derive(Debug, Error, Diagnostic)] -pub enum Error { - #[error("Filesystem error: {0}")] - #[diagnostic(code(flow::io_error), url(docsrs))] - Io(#[from] std::io::Error), - - #[error("Missing argument: {0}")] - #[diagnostic(code(flow::missing_argument), url(docsrs))] - MissingArgument(String), - - #[error("There is no active space set")] - #[diagnostic( - code(flow::missing_argument), - url(docsrs), - help(". Use --space to specify one specifically or register one with `flow register`.") - )] - NoActiveSpace, -} diff --git a/crates/flow-cli/src/lib.rs b/crates/flow-cli/src/lib.rs index c4538db..066998c 100644 --- a/crates/flow-cli/src/lib.rs +++ b/crates/flow-cli/src/lib.rs @@ -5,7 +5,6 @@ use crate::commands::{init, Command}; mod commands; mod common; -mod errors; mod extensions; mod printer; diff --git a/crates/flow-core/Cargo.toml b/crates/flow-core/Cargo.toml index ce44a37..c449b3f 100644 --- a/crates/flow-core/Cargo.toml +++ b/crates/flow-core/Cargo.toml @@ -13,9 +13,9 @@ categories.workspace = true rust-version.workspace = true [dependencies] +flow-errors = { workspace = true } tokio = { workspace = true } miette = { workspace = true } -thiserror = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } loro = "1.10.3" diff --git a/crates/flow-core/src/config/default.rs b/crates/flow-core/src/config/default.rs index ff859dd..2d17412 100644 --- a/crates/flow-core/src/config/default.rs +++ b/crates/flow-core/src/config/default.rs @@ -26,9 +26,9 @@ use std::path::PathBuf; use cross_xdg::BaseDirs; use miette::{IntoDiagnostic, Result}; -use crate::errors::Error; use crate::filesystem::Filesystem; use crate::space::Locator; +use crate::SpaceError; use super::traits::Config; use super::types::{RegisteredSpace, Settings, Spaces}; @@ -153,12 +153,12 @@ impl Config for DefaultConfig { // Check if already registered by name if self.spaces.spaces.iter().any(|s| s.name == name) { - return Err(Error::SpaceAlreadyRegistered(name.to_string()).into()); + return Err(SpaceError::AlreadyRegistered(name.to_string()).into()); } // Check if already registered by path if self.spaces.spaces.iter().any(|s| s.path == path) { - return Err(Error::SpacePathAlreadyRegistered(path.to_path_buf()).into()); + return Err(SpaceError::PathAlreadyRegistered(path.to_path_buf()).into()); } self.spaces.spaces.push(RegisteredSpace { @@ -174,7 +174,7 @@ impl Config for DefaultConfig { let index = self .find_index(&locator) - .ok_or_else(|| Error::SpaceNotRegistered(locator.clone()))?; + .ok_or_else(|| SpaceError::NotRegistered(locator.to_string()))?; let removed = self.spaces.spaces.remove(index); @@ -191,7 +191,7 @@ impl Config for DefaultConfig { let space = self .find(locator.clone()) - .ok_or(Error::SpaceNotRegistered(locator))?; + .ok_or_else(|| SpaceError::NotRegistered(locator.to_string()))?; self.spaces.active = Some(space.name.clone()); diff --git a/crates/flow-core/src/filesystem/local.rs b/crates/flow-core/src/filesystem/local.rs index b4fef91..1d88c6f 100644 --- a/crates/flow-core/src/filesystem/local.rs +++ b/crates/flow-core/src/filesystem/local.rs @@ -9,8 +9,8 @@ use std::path::Path; use miette::Result; use tokio::fs::{create_dir, create_dir_all, metadata, read, read_dir, read_to_string, try_exists, write}; -use crate::errors::Error; use crate::filesystem::traits::Filesystem; +use crate::IoError; /// A [`Filesystem`] implementation that operates on the local filesystem. /// @@ -47,7 +47,7 @@ impl Filesystem for LocalFilesystem { /// This is a non-blocking operation that queries the filesystem /// asynchronously. async fn exists(&self, path: impl AsRef + Send + Sync) -> Result { - try_exists(&path).await.map_err(|e| Error::Io(e).into()) + try_exists(&path).await.map_err(|e| IoError(e).into()) } /// Checks if a path is a directory using [`tokio::fs::metadata`]. @@ -55,7 +55,7 @@ impl Filesystem for LocalFilesystem { /// Returns an error if the path does not exist, unlike [`exists`](Self::exists) /// which returns `false` for non-existent paths. async fn is_dir(&self, path: impl AsRef + Send + Sync) -> Result { - let metadata = metadata(&path).await.map_err(Error::Io)?; + let metadata = metadata(&path).await.map_err(IoError)?; Ok(metadata.is_dir()) } @@ -69,8 +69,8 @@ impl Filesystem for LocalFilesystem { return Ok(false); } - let mut entries = read_dir(&path).await.map_err(Error::Io)?; - let is_empty = entries.next_entry().await.map_err(Error::Io)?.is_none(); + let mut entries = read_dir(&path).await.map_err(IoError)?; + let is_empty = entries.next_entry().await.map_err(IoError)?.is_none(); Ok(is_empty) } @@ -80,7 +80,7 @@ impl Filesystem for LocalFilesystem { /// This does **not** create parent directories. Use this only when /// the parent directory is known to exist. async fn create_dir(&self, path: impl AsRef + Send + Sync) -> Result<()> { - create_dir(&path).await.map_err(|e| Error::Io(e).into()) + create_dir(&path).await.map_err(|e| IoError(e).into()) } /// Creates a directory using [`tokio::fs::create_dir_all`]. @@ -88,7 +88,7 @@ impl Filesystem for LocalFilesystem { /// This does create parent directories. Use this only when /// the parent directory is known to exist. async fn create_dir_all(&self, path: impl AsRef + Send + Sync) -> Result<()> { - create_dir_all(&path).await.map_err(|e| Error::Io(e).into()) + create_dir_all(&path).await.map_err(|e| IoError(e).into()) } /// Writes content to a file using [`tokio::fs::write`]. @@ -100,9 +100,7 @@ impl Filesystem for LocalFilesystem { path: impl AsRef + Send + Sync, contents: impl AsRef<[u8]> + Send + Sync, ) -> Result<()> { - write(&path, &contents) - .await - .map_err(|e| Error::Io(e).into()) + write(&path, &contents).await.map_err(|e| IoError(e).into()) } /// Reads the entire file contents using [`tokio::fs::read`]. @@ -111,7 +109,7 @@ impl Filesystem for LocalFilesystem { /// using streaming APIs instead. async fn read(&self, path: impl AsRef + Send + Sync) -> Result> { let path = path.as_ref(); - read(path).await.map_err(|e| Error::Io(e).into()) + read(path).await.map_err(|e| IoError(e).into()) } /// Reads the entire file contents using [`tokio::fs::read_to_string`]. @@ -120,7 +118,7 @@ impl Filesystem for LocalFilesystem { /// using streaming APIs instead. async fn read_to_string(&self, path: impl AsRef + Send + Sync) -> Result { let path = path.as_ref(); - read_to_string(path).await.map_err(|e| Error::Io(e).into()) + read_to_string(path).await.map_err(|e| IoError(e).into()) } } diff --git a/crates/flow-core/src/lib.rs b/crates/flow-core/src/lib.rs index c1e25ad..b7bcfb8 100644 --- a/crates/flow-core/src/lib.rs +++ b/crates/flow-core/src/lib.rs @@ -92,10 +92,13 @@ mod filesystem; mod config; -mod errors; mod space; +pub use flow_errors::{IoError, SpaceError}; + pub use self::config::Config; -pub use self::errors::Error; pub use self::space::Locator; pub use self::space::Space; + +/// Type alias for backward compatibility. +pub type Error = SpaceError; diff --git a/crates/flow-core/src/space/default.rs b/crates/flow-core/src/space/default.rs index 0382483..2b5a2ed 100644 --- a/crates/flow-core/src/space/default.rs +++ b/crates/flow-core/src/space/default.rs @@ -46,9 +46,9 @@ use loro::LoroDoc; use miette::{ensure, IntoDiagnostic, Result}; use crate::{ - errors::Error, filesystem::Filesystem, space::{traits::Space, Locator, Metadata}, + SpaceError, }; /// The directory name where Flow stores space metadata. @@ -168,7 +168,7 @@ impl Space for DefaultSpace { let exists = fs.exists(path).await?; if exists { let is_dir = fs.is_dir(path).await?; - ensure!(is_dir, Error::NotADirectory(path.to_path_buf())); + ensure!(is_dir, SpaceError::NotADirectory(path.to_path_buf())); } else { fs.create_dir_all(path).await?; } @@ -178,10 +178,10 @@ impl Space for DefaultSpace { if !is_empty { let has_space = fs.exists(&flow_dir).await?; if has_space { - return Err(Error::AlreadyExists(path.to_path_buf()).into()); + return Err(SpaceError::AlreadyExists(path.to_path_buf()).into()); } - return Err(Error::DirectoryNotEmpty(path.to_path_buf()).into()); + return Err(SpaceError::DirectoryNotEmpty(path.to_path_buf()).into()); } let journal_dir = path.join(JOURNAL_DIR); diff --git a/crates/flow-errors/Cargo.toml b/crates/flow-errors/Cargo.toml new file mode 100644 index 0000000..41a924e --- /dev/null +++ b/crates/flow-errors/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "flow-errors" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +readme.workspace = true +keywords.workspace = true +categories.workspace = true +rust-version.workspace = true + +[dependencies] +miette = { workspace = true } +thiserror = { workspace = true } + +[lints] +workspace = true diff --git a/crates/flow-errors/src/cli.rs b/crates/flow-errors/src/cli.rs new file mode 100644 index 0000000..5681860 --- /dev/null +++ b/crates/flow-errors/src/cli.rs @@ -0,0 +1,45 @@ +//! Error types for CLI operations. +//! +//! This module defines the error types used by the Flow CLI. + +use miette::Diagnostic; +use thiserror::Error; + +/// Errors that can occur during CLI operations. +/// +/// This enum covers error conditions specific to the command-line interface, +/// such as missing arguments or configuration issues. +#[derive(Debug, Error, Diagnostic)] +pub enum CliError { + /// A required argument was not provided. + /// + /// This error occurs when a command requires an argument that + /// was not supplied by the user and could not be inferred. + /// + /// # Error Code + /// + /// `flow::missing_argument` + /// + /// # Fields + /// + /// * `0` - The name of the missing argument. + #[error("Missing argument: {0}")] + #[diagnostic(code(flow::missing_argument), url(docsrs))] + MissingArgument(String), + + /// No active space is set. + /// + /// This error occurs when a command requires an active space + /// but none has been set in the configuration. + /// + /// # Error Code + /// + /// `flow::no_active_space` + #[error("There is no active space set")] + #[diagnostic( + code(flow::no_active_space), + url(docsrs), + help("Use --space to specify one specifically or register one with `flow register`.") + )] + NoActiveSpace, +} diff --git a/crates/flow-errors/src/io.rs b/crates/flow-errors/src/io.rs new file mode 100644 index 0000000..464281c --- /dev/null +++ b/crates/flow-errors/src/io.rs @@ -0,0 +1,28 @@ +//! I/O error type for filesystem operations. + +use miette::Diagnostic; +use thiserror::Error; + +/// A filesystem operation failed. +/// +/// This type wraps low-level I/O errors from the operating system, +/// such as permission denied, disk full, or network errors when +/// accessing remote filesystems. +/// +/// # Error Code +/// +/// `flow::io_error` +/// +/// # Examples +/// +/// ``` +/// use flow_errors::IoError; +/// use std::io; +/// +/// let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "access denied"); +/// let error: IoError = io_error.into(); +/// ``` +#[derive(Debug, Error, Diagnostic)] +#[error("Filesystem error: {0}")] +#[diagnostic(code(flow::io_error))] +pub struct IoError(#[from] pub std::io::Error); diff --git a/crates/flow-errors/src/lib.rs b/crates/flow-errors/src/lib.rs new file mode 100644 index 0000000..4ca74b1 --- /dev/null +++ b/crates/flow-errors/src/lib.rs @@ -0,0 +1,39 @@ +//! Error types for the Flow notes and outliner system. +//! +//! This crate provides the error types used throughout the Flow ecosystem. +//! Errors use [`miette`] for rich diagnostic output with error codes, +//! help text, and source code context. +//! +//! # Overview +//! +//! This crate consolidates error types from across the Flow crates: +//! +//! - [`SpaceError`] - Errors related to space operations (initialization, loading, registration) +//! - [`CliError`] - Errors specific to CLI operations +//! - [`IoError`] - Wrapper for filesystem I/O errors +//! +//! # Error Handling +//! +//! All errors in this crate are compatible with [`miette`]'s diagnostic +//! system, which provides rich error reporting in CLI applications. +//! +//! # Examples +//! +//! ``` +//! use flow_errors::SpaceError; +//! use std::path::PathBuf; +//! +//! // Errors can be created directly +//! let error = SpaceError::NotFound(PathBuf::from("/nonexistent/path")); +//! +//! // They implement Display for human-readable messages +//! println!("Error: {}", error); +//! ``` + +mod cli; +mod io; +mod space; + +pub use cli::CliError; +pub use io::IoError; +pub use space::SpaceError; diff --git a/crates/flow-core/src/errors.rs b/crates/flow-errors/src/space.rs similarity index 66% rename from crates/flow-core/src/errors.rs rename to crates/flow-errors/src/space.rs index f4739e7..2ff003e 100644 --- a/crates/flow-core/src/errors.rs +++ b/crates/flow-errors/src/space.rs @@ -1,43 +1,14 @@ -//! Error types for flow-core operations. +//! Error types for space operations. //! -//! This module defines the error types used throughout the crate. +//! This module defines the error types used when working with Flow spaces. //! Errors use [`miette`] for rich diagnostic output with error codes, //! help text, and source code context. -//! -//! # Overview -//! -//! The [`Error`] enum represents all possible errors that can occur when -//! working with Flow spaces and filesystems. Each variant includes: -//! -//! - A human-readable error message -//! - A unique error code (e.g., `flow::io_error`) -//! - Contextual help text to guide users toward a solution -//! -//! # Error Handling -//! -//! All errors in this crate are compatible with [`miette`]'s diagnostic -//! system, which provides rich error reporting in CLI applications. -//! -//! # Examples -//! -//! ``` -//! use flow_core::Error; -//! use std::path::PathBuf; -//! -//! // Errors can be created directly -//! let error = Error::NotFound(PathBuf::from("/nonexistent/path")); -//! -//! // They implement Display for human-readable messages -//! println!("Error: {}", error); -//! ``` use std::path::PathBuf; use miette::Diagnostic; use thiserror::Error; -use crate::space::Locator; - /// Errors that can occur when working with spaces. /// /// This enum covers all error conditions that may arise during space @@ -48,26 +19,28 @@ use crate::space::Locator; /// /// | Variant | Error Code | Description | /// |---------|------------|-------------| -/// | [`Io`](Error::Io) | `flow::io_error` | Low-level filesystem errors | -/// | [`NotFound`](Error::NotFound) | `flow::path_not_found` | Path does not exist | -/// | [`NotADirectory`](Error::NotADirectory) | `flow::not_a_directory` | Path is not a directory | -/// | [`AlreadyExists`](Error::AlreadyExists) | `flow::already_exists` | Space already exists | -/// | [`DirectoryNotEmpty`](Error::DirectoryNotEmpty) | `flow::directory_not_empty` | Directory has contents | +/// | [`NotFound`](SpaceError::NotFound) | `flow::path_not_found` | Path does not exist | +/// | [`NotADirectory`](SpaceError::NotADirectory) | `flow::not_a_directory` | Path is not a directory | +/// | [`AlreadyExists`](SpaceError::AlreadyExists) | `flow::already_exists` | Space already exists | +/// | [`DirectoryNotEmpty`](SpaceError::DirectoryNotEmpty) | `flow::directory_not_empty` | Directory has contents | +/// | [`SpaceAlreadyRegistered`](SpaceError::SpaceAlreadyRegistered) | `flow::space_already_registered` | Name already in use | +/// | [`SpacePathAlreadyRegistered`](SpaceError::SpacePathAlreadyRegistered) | `flow::space_path_already_registered` | Path already registered | +/// | [`SpaceNotRegistered`](SpaceError::SpaceNotRegistered) | `flow::space_not_registered` | Space not found in config | /// /// # Examples /// /// Matching on specific error variants: /// /// ``` -/// use flow_core::Error; +/// use flow_errors::SpaceError; /// use std::path::PathBuf; /// -/// fn handle_error(error: Error) { +/// fn handle_error(error: SpaceError) { /// match error { -/// Error::NotFound(path) => { +/// SpaceError::NotFound(path) => { /// eprintln!("Path not found: {}", path.display()); /// } -/// Error::AlreadyExists(path) => { +/// SpaceError::AlreadyExists(path) => { /// eprintln!("Space already exists at: {}", path.display()); /// } /// _ => eprintln!("An error occurred: {}", error), @@ -75,30 +48,7 @@ use crate::space::Locator; /// } /// ``` #[derive(Debug, Error, Diagnostic)] -pub enum Error { - /// A filesystem operation failed. - /// - /// This variant wraps low-level I/O errors from the operating system, - /// such as permission denied, disk full, or network errors when - /// accessing remote filesystems. - /// - /// # Error Code - /// - /// `flow::io_error` - /// - /// # Examples - /// - /// ``` - /// use flow_core::Error; - /// use std::io; - /// - /// let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "access denied"); - /// let error: Error = io_error.into(); - /// ``` - #[error("Filesystem error: {0}")] - #[diagnostic(code(flow::io_error))] - Io(#[from] std::io::Error), - +pub enum SpaceError { /// The specified path does not exist. /// /// This error occurs when attempting to initialize or load a space @@ -184,7 +134,7 @@ pub enum Error { /// /// # Error Code /// - /// `flow::space_already_registered` + /// `flow::already_registered` /// /// # Fields /// @@ -195,7 +145,7 @@ pub enum Error { url(docsrs), help("Use a different name, or unregister the existing space first") )] - SpaceAlreadyRegistered(String), + AlreadyRegistered(String), /// A space at the given path is already registered. /// @@ -211,11 +161,11 @@ pub enum Error { /// * `0` - The path that is already registered. #[error("A space at path '{0}' is already registered")] #[diagnostic( - code(flow::space_path_already_registered), + code(flow::path_already_registered), url(docsrs), help("This space is already registered under a different name") )] - SpacePathAlreadyRegistered(PathBuf), + PathAlreadyRegistered(PathBuf), /// The specified space is not registered. /// @@ -228,12 +178,12 @@ pub enum Error { /// /// # Fields /// - /// * `0` - The locator used to find the space. + /// * `0` - The locator used to find the space (as a string). #[error("Space not registered: {0}")] #[diagnostic( - code(flow::space_not_registered), + code(flow::not_registered), url(docsrs), help("Register the space first with `flow register`") )] - SpaceNotRegistered(Locator), + NotRegistered(String), } From e64fd492c5f29e21cd3120df3f6ab2f94a9e7064 Mon Sep 17 00:00:00 2001 From: Michael Baudler Date: Thu, 22 Jan 2026 12:27:08 +0100 Subject: [PATCH 2/4] refactor: Add common crate for general shared code Factored out the existing Path extension trait. --- Cargo.lock | 5 +++++ Cargo.toml | 1 + crates/flow-cli/Cargo.toml | 1 + crates/flow-cli/src/commands/init.rs | 3 ++- crates/flow-cli/src/lib.rs | 1 - crates/flow-common/Cargo.toml | 18 ++++++++++++++++++ crates/flow-common/src/lib.rs | 3 +++ .../extensions.rs => flow-common/src/path.rs} | 0 8 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 crates/flow-common/Cargo.toml create mode 100644 crates/flow-common/src/lib.rs rename crates/{flow-cli/src/extensions.rs => flow-common/src/path.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 912e0f5..968b10d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,6 +598,7 @@ version = "0.1.0" dependencies = [ "clap", "console", + "flow-common", "flow-core", "flow-errors", "inquire", @@ -607,6 +608,10 @@ dependencies = [ "tokio", ] +[[package]] +name = "flow-common" +version = "0.1.0" + [[package]] name = "flow-core" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7a1a264..c1aac7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ rust-version = "1.91.1" [workspace.dependencies] flow-core = { path = "crates/flow-core" } flow-cli = { path = "crates/flow-cli" } +flow-common = { path = "crates/flow-common" } flow-errors = { path = "crates/flow-errors" } flow-app = { path = "crates/flow-app" } flow-tui = { path = "crates/flow-tui" } diff --git a/crates/flow-cli/Cargo.toml b/crates/flow-cli/Cargo.toml index a8b121c..3710236 100644 --- a/crates/flow-cli/Cargo.toml +++ b/crates/flow-cli/Cargo.toml @@ -13,6 +13,7 @@ categories.workspace = true rust-version.workspace = true [dependencies] +flow-common = { workspace = true } flow-core = { workspace = true } flow-errors = { workspace = true } clap = { workspace = true } diff --git a/crates/flow-cli/src/commands/init.rs b/crates/flow-cli/src/commands/init.rs index 3c8720c..f3a1e38 100644 --- a/crates/flow-cli/src/commands/init.rs +++ b/crates/flow-cli/src/commands/init.rs @@ -6,7 +6,8 @@ use inquire::Text; use miette::IntoDiagnostic; use serde::Serialize; -use crate::{commands::Command, common::OutputArgs, extensions::PathExt}; +use crate::{commands::Command, common::OutputArgs}; +use flow_common::PathExt; use flow_errors::CliError; #[derive(Args, Debug, Clone)] diff --git a/crates/flow-cli/src/lib.rs b/crates/flow-cli/src/lib.rs index 066998c..cd41fc5 100644 --- a/crates/flow-cli/src/lib.rs +++ b/crates/flow-cli/src/lib.rs @@ -5,7 +5,6 @@ use crate::commands::{init, Command}; mod commands; mod common; -mod extensions; mod printer; #[derive(Subcommand)] diff --git a/crates/flow-common/Cargo.toml b/crates/flow-common/Cargo.toml new file mode 100644 index 0000000..24ef718 --- /dev/null +++ b/crates/flow-common/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "flow-common" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +readme.workspace = true +keywords.workspace = true +categories.workspace = true +rust-version.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/flow-common/src/lib.rs b/crates/flow-common/src/lib.rs new file mode 100644 index 0000000..1e661fa --- /dev/null +++ b/crates/flow-common/src/lib.rs @@ -0,0 +1,3 @@ +mod path; + +pub use path::PathExt; diff --git a/crates/flow-cli/src/extensions.rs b/crates/flow-common/src/path.rs similarity index 100% rename from crates/flow-cli/src/extensions.rs rename to crates/flow-common/src/path.rs From 921bc1d45dd469aae23738f3b942741ae92b376f Mon Sep 17 00:00:00 2001 From: Michael Baudler Date: Thu, 22 Jan 2026 12:47:15 +0100 Subject: [PATCH 3/4] docs: Streamline code documentation --- crates/flow-app/src/lib.rs | 25 ++--- crates/flow-cli/src/commands.rs | 77 ++++++++++++++++ crates/flow-cli/src/commands/init.rs | 24 +++++ crates/flow-cli/src/common.rs | 33 +++++-- crates/flow-cli/src/lib.rs | 32 +++++++ crates/flow-cli/src/printer.rs | 33 ++++++- crates/flow-common/src/lib.rs | 9 ++ crates/flow-common/src/path.rs | 24 +++++ crates/flow-errors/src/space.rs | 131 ++------------------------- crates/flow-gui/src/lib.rs | 2 +- crates/flow-server/src/lib.rs | 2 +- crates/flow-tui/src/lib.rs | 2 +- crates/flow/src/main.rs | 22 ++++- 13 files changed, 259 insertions(+), 157 deletions(-) diff --git a/crates/flow-app/src/lib.rs b/crates/flow-app/src/lib.rs index 44bf58f..3d5adaa 100644 --- a/crates/flow-app/src/lib.rs +++ b/crates/flow-app/src/lib.rs @@ -1,16 +1,9 @@ -/// Adds two numbers together. -#[must_use] -pub const fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +//! Flow application layer. +//! +//! This crate provides the high-level application logic for Flow, coordinating +//! between the core library and the various frontends (CLI, TUI, GUI, server). +//! +//! # Status +//! +//! This crate is currently a placeholder and will be implemented as the +//! application layer matures. diff --git a/crates/flow-cli/src/commands.rs b/crates/flow-cli/src/commands.rs index 74228a5..5083e94 100644 --- a/crates/flow-cli/src/commands.rs +++ b/crates/flow-cli/src/commands.rs @@ -1,27 +1,104 @@ +//! CLI command definitions and the [`Command`] trait. +//! +//! This module defines the trait that all CLI commands implement, providing +//! a consistent pattern for argument handling, execution, and output. + use miette::Result; use crate::{common::OutputArgs, printer::Printer}; pub mod init; +/// A CLI command that can be executed. +/// +/// This trait defines the lifecycle of a command: +/// +/// 1. **Construction** - Create the command with parsed arguments via [`new`](Self::new) +/// 2. **Interactive prompting** - Gather missing arguments via [`interactive`](Self::interactive) +/// 3. **Execution** - Perform the command's work via [`execute`](Self::execute) +/// 4. **Output** - Display results via [`finalize`](Self::finalize) or JSON +/// +/// The [`run`](Self::run) method orchestrates this lifecycle automatically. +/// +/// # Type Parameters +/// +/// - `Args` - The clap arguments struct for this command +/// - `Output` - The result type, must be serializable for JSON output +/// +/// # Example +/// +/// ```ignore +/// struct MyCommand { args: MyArgs } +/// +/// impl Command for MyCommand { +/// type Args = MyArgs; +/// type Output = MyOutput; +/// +/// fn new(args: Self::Args) -> Self { +/// Self { args } +/// } +/// +/// // ... implement other required methods +/// } +/// ``` pub trait Command: Sized { + /// The argument type for this command, typically a clap `Args` struct. type Args; + + /// The output type produced by this command. + /// + /// Must implement [`Serialize`](serde::Serialize) for JSON output support. type Output: serde::Serialize; + /// Creates a new command instance from parsed arguments. fn new(args: Self::Args) -> Self; + /// Returns the output formatting arguments. fn output_args(&self) -> &OutputArgs; + /// Creates a printer configured with the command's output settings. fn printer(&self) -> Printer { self.output_args().printer() } + /// Prompts the user for any missing required arguments. + /// + /// This method is called when running in interactive mode (not JSON output). + /// Implementations should use `inquire` or similar to gather missing values. + /// + /// # Errors + /// + /// Returns an error if prompting fails or the user cancels. async fn interactive(&mut self) -> Result<()>; + /// Executes the command's main logic. + /// + /// This method performs the actual work of the command and returns + /// the result. It is called after [`interactive`](Self::interactive) + /// has gathered any missing arguments. + /// + /// # Errors + /// + /// Returns an error if the command fails. async fn execute(&mut self) -> Result; + /// Displays the command's output in human-readable format. + /// + /// This method is called after successful execution when not in JSON mode. + /// Use the [`printer`](Self::printer) to format output consistently. fn finalize(&self, output: &Self::Output); + /// Runs the complete command lifecycle. + /// + /// This method orchestrates the command execution: + /// + /// 1. If not in JSON mode, calls [`interactive`](Self::interactive) for prompts + /// 2. Calls [`execute`](Self::execute) to perform the work + /// 3. Outputs results as JSON or calls [`finalize`](Self::finalize) + /// + /// # Errors + /// + /// Returns an error if any step fails. async fn run(&mut self) -> Result<()> { if !self.output_args().json { self.interactive().await?; diff --git a/crates/flow-cli/src/commands/init.rs b/crates/flow-cli/src/commands/init.rs index f3a1e38..fdbb5a1 100644 --- a/crates/flow-cli/src/commands/init.rs +++ b/crates/flow-cli/src/commands/init.rs @@ -1,3 +1,22 @@ +//! The `init` command for creating new Flow spaces. +//! +//! This module implements the `flow init` command, which initializes a new +//! Flow space at a specified path. The command supports both interactive +//! and non-interactive modes. +//! +//! # Examples +//! +//! ```bash +//! # Interactive mode - prompts for path and name +//! flow init +//! +//! # Non-interactive with arguments +//! flow init ./my-notes --name personal +//! +//! # JSON output for scripting +//! flow init ./my-notes --name personal --json +//! ``` + use std::path::PathBuf; use clap::Args; @@ -10,6 +29,7 @@ use crate::{commands::Command, common::OutputArgs}; use flow_common::PathExt; use flow_errors::CliError; +/// Command-line arguments for the `init` command. #[derive(Args, Debug, Clone)] pub struct Arguments { #[command(flatten)] @@ -27,12 +47,16 @@ pub struct Arguments { pub no_register: bool, } +/// Output of a successful `init` command. #[derive(Debug, Clone, Default, Serialize)] pub struct Output { + /// The name assigned to the space. pub name: String, + /// The filesystem path where the space was created. pub path: PathBuf, } +/// The `init` command implementation. pub struct Init { args: Arguments, } diff --git a/crates/flow-cli/src/common.rs b/crates/flow-cli/src/common.rs index 22051e1..84b9f1a 100644 --- a/crates/flow-cli/src/common.rs +++ b/crates/flow-cli/src/common.rs @@ -1,3 +1,8 @@ +//! Common argument types shared across CLI commands. +//! +//! This module provides reusable argument structs that can be embedded +//! in command-specific argument types using clap's `#[command(flatten)]`. + use clap::Args; use flow_core::{Config, Space}; use miette::Result; @@ -6,7 +11,10 @@ use std::path::PathBuf; use crate::printer::Printer; use flow_errors::CliError; -/// Output formatting arguments - available for ALL commands. +/// Output formatting arguments available for all commands. +/// +/// These arguments control how command output is displayed and can be +/// embedded in any command's arguments using `#[command(flatten)]`. #[derive(Args, Debug, Clone)] pub struct OutputArgs { /// Output in JSON format @@ -30,36 +38,41 @@ impl OutputArgs { } } -/// Arguments for commands that operate on existing spaces. +/// Arguments for commands that operate on an existing space. +/// +/// This struct combines output formatting with space selection, providing +/// a consistent way to target a specific space or use the active default. #[derive(Args, Debug, Clone)] pub struct SpaceArgs { - /// Embedded output arguments + /// Output formatting options. #[command(flatten)] pub output: OutputArgs, - /// Target specific space by name or path (overrides active space) + /// Target a specific space by name or path, overriding the active space. #[arg(long)] pub space: Option, } impl SpaceArgs { - /// Create a printer from these space arguments. + /// Creates a printer configured with these arguments' output settings. #[must_use] #[allow(dead_code)] pub const fn printer(&self) -> Printer { self.output.printer() } - /// Load the space based on the provided arguments. + /// Loads the target space based on these arguments. + /// + /// # Resolution Order /// - /// Resolution order: - /// 1. If `--space` is provided and is a valid path, load from that path - /// 2. If `--space` is provided and matches a registered space name, load that space + /// 1. If `--space` is a valid filesystem path, load from that path + /// 2. If `--space` matches a registered space name, load that space /// 3. Otherwise, load the currently active space from config /// /// # Errors /// - /// Returns an error if no space can be found or loaded. + /// Returns [`CliError::NoActiveSpace`] if no space can be determined, + /// or a space loading error if the space cannot be read. #[allow(dead_code)] pub async fn load_space(&self) -> Result { if let Some(name_or_path) = &self.space { diff --git a/crates/flow-cli/src/lib.rs b/crates/flow-cli/src/lib.rs index cd41fc5..dfbf8b3 100644 --- a/crates/flow-cli/src/lib.rs +++ b/crates/flow-cli/src/lib.rs @@ -1,3 +1,30 @@ +//! Command-line interface for Flow. +//! +//! This crate provides the CLI commands for Flow, including space +//! initialization, management, and note operations. Commands support +//! both interactive and non-interactive (JSON output) modes. +//! +//! # Architecture +//! +//! Commands implement the `Command` trait, which provides a consistent +//! pattern for: +//! +//! - Interactive prompting when arguments are missing +//! - Execution logic +//! - Output formatting (human-readable or JSON) +//! +//! # Usage +//! +//! The main entry point is the [`run`] function, which dispatches to +//! the appropriate command handler based on the [`Commands`] enum. +//! +//! ```ignore +//! use flow_cli::{Commands, run}; +//! +//! let cmd = Commands::Init(init::Arguments { /* ... */ }); +//! run(cmd).await?; +//! ``` + use clap::Subcommand; use miette::Result; @@ -7,8 +34,13 @@ mod commands; mod common; mod printer; +/// Available CLI commands. +/// +/// Each variant corresponds to a subcommand that can be invoked from +/// the command line (e.g., `flow init`). #[derive(Subcommand)] pub enum Commands { + /// Initialize a new Flow space. Init(init::Arguments), } diff --git a/crates/flow-cli/src/printer.rs b/crates/flow-cli/src/printer.rs index 18b0c52..f31594a 100644 --- a/crates/flow-cli/src/printer.rs +++ b/crates/flow-cli/src/printer.rs @@ -1,7 +1,12 @@ +//! Output formatting and printing for the CLI. +//! +//! This module provides the [`Printer`] struct, which handles all CLI output +//! with support for different output modes (normal, JSON, verbose, quiet). + use console::{style, Emoji, Term}; use miette::{IntoDiagnostic, Result}; -// Emojis with fallbacks for terminals that don't support them +// Emojis with fallbacks for terminals that don't support them. static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", "* "); static INFO: Emoji<'_, '_> = Emoji("ℹ️ ", "[i] "); static SUCCESS: Emoji<'_, '_> = Emoji("✅ ", "[+] "); @@ -10,7 +15,13 @@ static ERROR: Emoji<'_, '_> = Emoji("❌ ", "[x] "); static DEBUG: Emoji<'_, '_> = Emoji("🔍 ", "[?] "); static ARROW: Emoji<'_, '_> = Emoji("→ ", "-> "); -/// Handles all CLI output printing with support for JSON, verbose, and quiet modes. +/// Handles CLI output with support for multiple output modes. +/// +/// The printer respects three flags that control output behavior: +/// +/// - **JSON mode**: Suppresses human-readable output; only [`json`](Self::json) produces output +/// - **Verbose mode**: Enables [`verbose`](Self::verbose) and [`debug`](Self::debug) output +/// - **Quiet mode**: Suppresses all non-error output #[derive(Debug, Clone)] pub struct Printer { json: bool, @@ -19,52 +30,61 @@ pub struct Printer { } impl Printer { + /// Creates a new printer with the specified output modes. #[must_use] pub const fn new(json: bool, verbose: bool, quiet: bool) -> Self { Self { json, verbose, quiet } } + /// Returns `true` if JSON output mode is enabled. #[must_use] pub const fn is_json(&self) -> bool { self.json } + /// Prints a plain message to stdout. pub fn print(&self, message: impl AsRef) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(message.as_ref()); } } + /// Prints a success message with a green checkmark. pub fn success(&self, message: impl AsRef) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!("{}{}", SUCCESS, style(message.as_ref()).green().bold())); } } + /// Prints an informational message with a blue info icon. pub fn info(&self, message: impl AsRef) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!("{}{}", INFO, style(message.as_ref()).cyan())); } } + /// Prints a warning message with a yellow warning icon. pub fn warning(&self, message: impl AsRef) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!("{}{}", WARN, style(message.as_ref()).yellow().bold())); } } + /// Prints a step indicator with an arrow prefix. pub fn step(&self, message: impl AsRef) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!("{}{}", ARROW, style(message.as_ref()).dim())); } } + /// Prints a verbose message (only shown with `--verbose` flag). pub fn verbose(&self, message: impl AsRef) { if self.verbose && !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!("{}{}", DEBUG, style(message.as_ref()).dim())); } } + /// Prints a debug key-value pair (only shown with `--verbose` flag). pub fn debug(&self, label: impl Into, value: impl Into) { if self.verbose && !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!( @@ -76,18 +96,21 @@ impl Printer { } } + /// Prints an error message to stderr with a red error icon. pub fn error(&self, message: impl AsRef) { if !self.quiet && !self.json { let _ = Term::stderr().write_line(&format!("{}{}", ERROR, style(message.as_ref()).red().bold())); } } + /// Prints a section heading with a sparkle icon. pub fn heading(&self, heading: impl Into) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!("{}{}", SPARKLE, style(heading.into()).bold().underlined())); } } + /// Prints a key-value pair with indentation. pub fn kv(&self, key: impl Into, value: impl Into) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(&format!( @@ -98,12 +121,18 @@ impl Printer { } } + /// Prints a blank line. pub fn blank(&self) { if !self.quiet && !self.json { let _ = Term::stdout().write_line(""); } } + /// Outputs a value as pretty-printed JSON (only in JSON mode). + /// + /// # Errors + /// + /// Returns an error if serialization fails. pub fn json(&self, value: &T) -> Result<()> { if self.json { let json = serde_json::to_string_pretty(value).into_diagnostic()?; diff --git a/crates/flow-common/src/lib.rs b/crates/flow-common/src/lib.rs index 1e661fa..b70ea7e 100644 --- a/crates/flow-common/src/lib.rs +++ b/crates/flow-common/src/lib.rs @@ -1,3 +1,12 @@ +//! Common utilities shared across Flow crates. +//! +//! This crate provides shared functionality used by multiple Flow crates, +//! including path manipulation utilities and other cross-cutting concerns. +//! +//! # Re-exports +//! +//! - [`PathExt`] - Extension trait for [`std::path::Path`] with normalization support. + mod path; pub use path::PathExt; diff --git a/crates/flow-common/src/path.rs b/crates/flow-common/src/path.rs index 5848f20..b142e4d 100644 --- a/crates/flow-common/src/path.rs +++ b/crates/flow-common/src/path.rs @@ -1,6 +1,30 @@ +//! Path manipulation utilities. +//! +//! This module provides extension traits for working with filesystem paths, +//! particularly for cross-platform path normalization. + use std::path::Path; +/// Extension trait for [`Path`] providing additional utility methods. +/// +/// This trait adds methods to [`Path`] that are useful across the Flow +/// codebase, particularly for displaying paths in a user-friendly format. pub trait PathExt { + /// Returns a normalized string representation of the path. + /// + /// On Windows, this removes the extended-length path prefix (`\\?\`) + /// that can appear when paths are canonicalized. On other platforms, + /// this simply returns the path's display string. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use flow_common::PathExt; + /// + /// let path = Path::new("/home/user/notes"); + /// assert_eq!(path.normalize(), "/home/user/notes"); + /// ``` fn normalize(&self) -> String; } diff --git a/crates/flow-errors/src/space.rs b/crates/flow-errors/src/space.rs index 2ff003e..69a1f8d 100644 --- a/crates/flow-errors/src/space.rs +++ b/crates/flow-errors/src/space.rs @@ -11,56 +11,11 @@ use thiserror::Error; /// Errors that can occur when working with spaces. /// -/// This enum covers all error conditions that may arise during space -/// initialization, loading, and filesystem operations. Each variant -/// provides detailed diagnostic information through [`miette`]. -/// -/// # Variants -/// -/// | Variant | Error Code | Description | -/// |---------|------------|-------------| -/// | [`NotFound`](SpaceError::NotFound) | `flow::path_not_found` | Path does not exist | -/// | [`NotADirectory`](SpaceError::NotADirectory) | `flow::not_a_directory` | Path is not a directory | -/// | [`AlreadyExists`](SpaceError::AlreadyExists) | `flow::already_exists` | Space already exists | -/// | [`DirectoryNotEmpty`](SpaceError::DirectoryNotEmpty) | `flow::directory_not_empty` | Directory has contents | -/// | [`SpaceAlreadyRegistered`](SpaceError::SpaceAlreadyRegistered) | `flow::space_already_registered` | Name already in use | -/// | [`SpacePathAlreadyRegistered`](SpaceError::SpacePathAlreadyRegistered) | `flow::space_path_already_registered` | Path already registered | -/// | [`SpaceNotRegistered`](SpaceError::SpaceNotRegistered) | `flow::space_not_registered` | Space not found in config | -/// -/// # Examples -/// -/// Matching on specific error variants: -/// -/// ``` -/// use flow_errors::SpaceError; -/// use std::path::PathBuf; -/// -/// fn handle_error(error: SpaceError) { -/// match error { -/// SpaceError::NotFound(path) => { -/// eprintln!("Path not found: {}", path.display()); -/// } -/// SpaceError::AlreadyExists(path) => { -/// eprintln!("Space already exists at: {}", path.display()); -/// } -/// _ => eprintln!("An error occurred: {}", error), -/// } -/// } -/// ``` +/// Each variant provides diagnostic information through [`miette`], +/// including error codes, help text, and documentation links. #[derive(Debug, Error, Diagnostic)] pub enum SpaceError { - /// The specified path does not exist. - /// - /// This error occurs when attempting to initialize or load a space - /// from a path that does not exist on the filesystem. - /// - /// # Error Code - /// - /// `flow::path_not_found` - /// - /// # Fields - /// - /// * `0` - The path that was not found. + /// The specified path does not exist on the filesystem. #[error("Path does not exist: {0}")] #[diagnostic( code(flow::path_not_found), @@ -69,19 +24,7 @@ pub enum SpaceError { )] NotFound(PathBuf), - /// A space already exists at the specified path. - /// - /// This error occurs when attempting to initialize a new space in - /// a directory that already contains a `.flow` directory, indicating - /// an existing space. - /// - /// # Error Code - /// - /// `flow::already_exists` - /// - /// # Fields - /// - /// * `0` - The path where the space already exists. + /// A space already exists at the specified path (`.flow` directory found). #[error("A space already exists at: {0}")] #[diagnostic( code(flow::already_exists), @@ -90,35 +33,12 @@ pub enum SpaceError { )] AlreadyExists(PathBuf), - /// The specified path is not a directory. - /// - /// This error occurs when a path exists but is a file rather than - /// a directory. Spaces can only be initialized in directories. - /// - /// # Error Code - /// - /// `flow::not_a_directory` - /// - /// # Fields - /// - /// * `0` - The path that is not a directory. + /// The path exists but is not a directory. #[error("Path is not a directory: {0}")] #[diagnostic(code(flow::not_a_directory), url(docsrs), help("Make sure the path is a directory"))] NotADirectory(PathBuf), - /// The directory is not empty. - /// - /// This error occurs when attempting to initialize a space in a - /// directory that contains files or subdirectories. Spaces should - /// be initialized in empty directories to avoid conflicts. - /// - /// # Error Code - /// - /// `flow::directory_not_empty` - /// - /// # Fields - /// - /// * `0` - The path to the non-empty directory. + /// The directory contains files or subdirectories. #[error("Directory is not empty: {0}")] #[diagnostic( code(flow::directory_not_empty), @@ -127,18 +47,7 @@ pub enum SpaceError { )] DirectoryNotEmpty(PathBuf), - /// A space with the given name is already registered. - /// - /// This error occurs when attempting to register a space with a name - /// that is already in use by another registered space. - /// - /// # Error Code - /// - /// `flow::already_registered` - /// - /// # Fields - /// - /// * `0` - The name that is already registered. + /// A space with the given name is already registered in the config. #[error("A space with the name '{0}' is already registered")] #[diagnostic( code(flow::space_already_registered), @@ -147,18 +56,7 @@ pub enum SpaceError { )] AlreadyRegistered(String), - /// A space at the given path is already registered. - /// - /// This error occurs when attempting to register a space at a path - /// that is already registered under a different name. - /// - /// # Error Code - /// - /// `flow::space_path_already_registered` - /// - /// # Fields - /// - /// * `0` - The path that is already registered. + /// A space at the given path is already registered under a different name. #[error("A space at path '{0}' is already registered")] #[diagnostic( code(flow::path_already_registered), @@ -167,18 +65,7 @@ pub enum SpaceError { )] PathAlreadyRegistered(PathBuf), - /// The specified space is not registered. - /// - /// This error occurs when attempting to operate on a space that - /// has not been registered in the configuration. - /// - /// # Error Code - /// - /// `flow::space_not_registered` - /// - /// # Fields - /// - /// * `0` - The locator used to find the space (as a string). + /// The specified space was not found in the configuration. #[error("Space not registered: {0}")] #[diagnostic( code(flow::not_registered), diff --git a/crates/flow-gui/src/lib.rs b/crates/flow-gui/src/lib.rs index 4dd8763..5ceb6ed 100644 --- a/crates/flow-gui/src/lib.rs +++ b/crates/flow-gui/src/lib.rs @@ -6,7 +6,7 @@ use miette::Result; /// /// # Errors /// -/// Returns an error if the GUI fails to initialize or encounters a unrecoverable runtime error. +/// Returns an error if the GUI fails to initialize or encounters an unrecoverable runtime error. pub async fn run() -> Result<()> { todo!("Running GUI"); } diff --git a/crates/flow-server/src/lib.rs b/crates/flow-server/src/lib.rs index f0bf7ae..7ea1eab 100644 --- a/crates/flow-server/src/lib.rs +++ b/crates/flow-server/src/lib.rs @@ -6,7 +6,7 @@ use miette::Result; /// /// # Errors /// -/// Returns an error if the server fails to start or encounters a unrecoverable runtime error. +/// Returns an error if the server fails to start or encounters an unrecoverable runtime error. pub async fn run() -> Result<()> { todo!("Running server"); } diff --git a/crates/flow-tui/src/lib.rs b/crates/flow-tui/src/lib.rs index ca10234..64b813f 100644 --- a/crates/flow-tui/src/lib.rs +++ b/crates/flow-tui/src/lib.rs @@ -7,7 +7,7 @@ use miette::Result; /// # Errors /// /// Returns an error if the TUI fails to initialize or encounters -/// a unrecoverable runtime error during execution. +/// an unrecoverable runtime error during execution. pub async fn run() -> Result<()> { todo!("Running TUI"); } diff --git a/crates/flow/src/main.rs b/crates/flow/src/main.rs index 6f3b019..e0aa68b 100644 --- a/crates/flow/src/main.rs +++ b/crates/flow/src/main.rs @@ -1,7 +1,17 @@ +//! Flow - Note taking for developers. +//! +//! This is the main entry point for the Flow application. It provides a unified +//! binary that can operate in multiple modes: +//! +//! - **CLI** (default) - Command-line interface for space and note management +//! - **TUI** - Terminal user interface (with `tui` feature) +//! - **GUI** - Desktop graphical interface (with `gui` feature) +//! - **Server** - HTTP server mode (with `server` feature) + use clap::{CommandFactory, Parser, Subcommand}; use miette::{IntoDiagnostic, Result}; -/// Build the version string with compiled features. +/// Builds the version string, appending enabled feature flags. fn version() -> &'static str { let version = env!("CARGO_PKG_VERSION"); let features: &[&str] = &[ @@ -22,26 +32,30 @@ fn version() -> &'static str { } } -/// Commands for the fat binary. +/// Top-level CLI argument parser. #[derive(Parser)] #[command(name = "flow")] #[command(version = version())] #[command(about = "Flow - Note taking for developers")] struct Flow { - /// Sub-commands. + /// The subcommand to execute. #[command(subcommand)] command: Option, } -/// Sub-commands. +/// Available subcommands for the Flow binary. #[derive(Subcommand)] enum Commands { + /// Launch the terminal user interface. #[cfg(feature = "tui")] Tui, + /// Launch the desktop graphical interface. #[cfg(feature = "gui")] Gui, + /// Start the HTTP server. #[cfg(feature = "server")] Serve, + /// CLI commands (init, etc.). #[command(flatten)] Cli(flow_cli::Commands), } From f6b43cabc4fd95993d1a9c663587f1fdc342f9a1 Mon Sep 17 00:00:00 2001 From: Michael Baudler Date: Thu, 22 Jan 2026 12:53:06 +0100 Subject: [PATCH 4/4] chore: Add error and common crate to allow AGPL-3.0 for cargo deny --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index c0c6351..a286730 100644 --- a/deny.toml +++ b/deny.toml @@ -44,6 +44,8 @@ allow = [ # must make their source code available to users (see AGPL Section 13). exceptions = [ { name = "flow", allow = ["AGPL-3.0-or-later"] }, + { name = "flow-errors", allow = ["AGPL-3.0-or-later"] }, + { name = "flow-common", allow = ["AGPL-3.0-or-later"] }, { name = "flow-app", allow = ["AGPL-3.0-or-later"] }, { name = "flow-cli", allow = ["AGPL-3.0-or-later"] }, { name = "flow-core", allow = ["AGPL-3.0-or-later"] },