opys-engine 0.12.0

Core library for opys — a file-based inventory of typed markdown documents with a verify gate and SQL query layer
Documentation
//! opys — a file-based inventory of typed markdown documents: one markdown file
//! per document, with YAML frontmatter, stable IDs, tags, configurable types,
//! test plans, and a `verify` gate for CI.
//!
//! The binary is a thin wrapper around [`run`]. The modules are public so the
//! crate can be used as a library.

pub mod backend;
pub mod body;
pub mod cli;
pub mod commands;
pub mod config;
pub mod doc;
pub mod error;
pub mod file_refs;
pub mod frontmatter;
pub mod links;
pub mod mdprism;
pub mod palette;
pub mod project;
pub mod project_config;
pub mod refs;
pub mod retired;
pub mod rules;
pub mod store;
pub mod templates;

pub use error::{OpysError, Result};
pub use frontmatter::Frontmatter;
pub use project::Project;

use cli::{Cli, Command};

/// Shared invocation context: where the inventory lives, global flags, and the
/// storage backend the commands load/flush through.
pub struct Ctx {
    pub root: String,
    pub no_sync: bool,
    pub backend: Box<dyn backend::Backend>,
}

impl Ctx {
    pub fn open(&self) -> Result<Project> {
        Project::open(&self.root)
    }

    /// Load the corpus into a working store via the injected backend.
    pub fn load(&self, prj: &Project) -> Result<(store::Store, Vec<String>)> {
        self.backend.load(prj)
    }

    /// Flush the store's changes via the injected backend.
    pub fn flush(&self, prj: &Project, store: store::Store) -> Result<()> {
        self.backend.flush(prj, store)
    }
}

/// Execute a parsed CLI invocation, returning the process exit code.
///
/// `verify` returns `1` when it finds problems (the CI-gate contract); all
/// other commands return `0` on success and surface failures as
/// [`OpysError`], which the binary maps to exit code `2`.
pub fn run(cli: Cli, backend: Box<dyn backend::Backend>) -> Result<i32> {
    let ctx = Ctx {
        root: cli.root,
        no_sync: cli.no_sync,
        backend,
    };
    match cli.command {
        Command::Init => {
            commands::init::run(&ctx)?;
            Ok(0)
        }
        Command::New {
            type_name,
            title,
            tags,
            status,
            features,
            reason,
            field,
        } => {
            commands::new::run(
                &ctx,
                &type_name,
                &title,
                &tags,
                &status,
                &features,
                reason.as_deref(),
                &field,
            )?;
            Ok(0)
        }
        Command::Import { type_name, file } => {
            commands::import::run(&ctx, &type_name, &file)?;
            Ok(0)
        }
        Command::Show { id, refs } => {
            commands::show::run(&ctx, &id, refs)?;
            Ok(0)
        }
        Command::List {
            type_name,
            tag,
            status,
            field,
            format,
        } => {
            commands::list::run(
                &ctx,
                type_name.as_deref(),
                tag.as_deref(),
                status.as_deref(),
                &field,
                format,
            )?;
            Ok(0)
        }
        Command::SetStatus {
            ids,
            status,
            reason,
        } => {
            commands::set_status::run(&ctx, &ids, &status, reason.as_deref())?;
            Ok(0)
        }
        Command::Tag { ids, add, remove } => {
            commands::tag::run(&ctx, &ids, add.as_deref(), remove.as_deref())?;
            Ok(0)
        }
        Command::Retire { ids, reason } => {
            commands::retire::run(&ctx, &ids, &reason)?;
            Ok(0)
        }
        Command::Block { ids, by } => {
            commands::block::block(&ctx, &ids, &by)?;
            Ok(0)
        }
        Command::Unblock { ids, by } => {
            commands::block::unblock(&ctx, &ids, &by)?;
            Ok(0)
        }
        Command::Verify => commands::verify::run(&ctx),
        Command::Sync => {
            commands::sync::run_command(&ctx)?;
            Ok(0)
        }
        Command::Tags { keys } => {
            commands::tags::run(&ctx, keys)?;
            Ok(0)
        }
        Command::Stats { plain } => {
            commands::stats::run(&ctx, plain)?;
            Ok(0)
        }
        Command::Query {
            sql,
            plain,
            write,
            stdin,
        } => {
            commands::query::run(&ctx, &sql, plain, write, stdin)?;
            Ok(0)
        }
        #[cfg(feature = "history")]
        Command::History { id } => {
            commands::history::run(&ctx, &id)?;
            Ok(0)
        }
        Command::Close { ids, force } => {
            commands::close::run(&ctx, &ids, force)?;
            Ok(0)
        }
        Command::Renumber { base } => {
            commands::renumber::run(&ctx, base.as_deref())?;
            Ok(0)
        }
        Command::Cleanup => {
            commands::cleanup::run(&ctx)?;
            Ok(0)
        }
        Command::Config(cmd) => match cmd {
            cli::ConfigCommand::Init => {
                commands::config::init(&ctx)?;
                Ok(0)
            }
            cli::ConfigCommand::Validate => commands::config::validate(&ctx),
        },
        Command::AgentRules { tool, stdout } => {
            commands::agent_rules::run(&ctx, tool, stdout)?;
            Ok(0)
        }
        // The TUI is launched by the binary (opys-tui crate); it intercepts this
        // command before calling run(), so this arm is unreachable in practice.
        #[cfg(feature = "tui")]
        Command::Tui { .. } => Err(crate::error::usage(
            "the tui is launched by the opys binary, not opys::run",
        )),
    }
}