Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ 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" }
flow-gui = { path = "crates/flow-gui" }
Expand Down
25 changes: 9 additions & 16 deletions crates/flow-app/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion crates/flow-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ categories.workspace = true
rust-version.workspace = true

[dependencies]
flow-common = { workspace = true }
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"] }
Expand Down
77 changes: 77 additions & 0 deletions crates/flow-cli/src/commands.rs
Original file line number Diff line number Diff line change
@@ -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<Self::Output>;

/// 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?;
Expand Down
32 changes: 29 additions & 3 deletions crates/flow-cli/src/commands/init.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -6,8 +25,11 @@ 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};
use flow_common::PathExt;
use flow_errors::CliError;

/// Command-line arguments for the `init` command.
#[derive(Args, Debug, Clone)]
pub struct Arguments {
#[command(flatten)]
Expand All @@ -25,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,
}
Expand Down Expand Up @@ -86,15 +112,15 @@ 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
.args
.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 {
Expand Down
38 changes: 26 additions & 12 deletions crates/flow-cli/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
//! 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;
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.
/// 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
Expand All @@ -29,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<String>,
}

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<Space> {
if let Some(name_or_path) = &self.space {
Expand All @@ -74,7 +88,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
}
Expand Down
21 changes: 0 additions & 21 deletions crates/flow-cli/src/errors.rs

This file was deleted.

21 changes: 0 additions & 21 deletions crates/flow-cli/src/extensions.rs

This file was deleted.

Loading