use std::ffi::OsString;
use std::fmt;
use std::path::PathBuf;
use thiserror::Error;
use crate::{
ConfigError, EntryAddress, EntryAddressError, EntryArtifactPathError, EntryAtomError,
EntryDirectoryError, EntryParseError, FrostError, GeneratedLinkError, LockError, TideError,
TutorialSettings, UpstreamError, WitnessError,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OpenTideTutorial {
pub(crate) frost_commit_tide: bool,
pub(crate) frost_bootstrap_tide: bool,
pub(crate) bootstrap: bool,
}
impl OpenTideTutorial {
pub(crate) fn new(settings: Option<TutorialSettings>, bootstrap: bool) -> Self {
let Some(settings) = settings else {
return Self { frost_commit_tide: false, frost_bootstrap_tide: false, bootstrap };
};
Self {
frost_commit_tide: settings.frost_commit_tide,
frost_bootstrap_tide: settings.frost_bootstrap_tide,
bootstrap,
}
}
}
impl fmt::Display for OpenTideTutorial {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.frost_commit_tide {
return Ok(());
}
writeln!(formatter)?;
writeln!(formatter)?;
writeln!(formatter, "Tutorial:")?;
writeln!(
formatter,
"A tide is the review worklist for differences between the waterline and frostline.",
)?;
if self.bootstrap && self.frost_bootstrap_tide {
writeln!(
formatter,
"This frost path is still at empty version 0, so the first commit compares",
)?;
writeln!(formatter, "the full lake to an empty frostline.")?;
}
writeln!(formatter, "Inspect the work with `sirno tide status`.")?;
writeln!(formatter, "Resolve reviewed work with `sirno resolve ...`,",)?;
writeln!(
formatter,
"or choose the current lake as the baseline with `sirno commit --unsafe-resolve-all`.",
)?;
write!(formatter, "Remove `[tutorial]` from Sirno.toml, or set tutorial knobs to false,",)?;
write!(formatter, " to silence tutorial text.")
}
}
#[derive(Debug, Error)]
pub enum CommandError {
#[error("frost is already configured at {0}")]
FrostAlreadyConfigured(PathBuf),
#[error("frost is not configured; run `sirno frost init` first")]
FrostNotConfigured,
#[error("frost version {0} is checked out immutably; use checkout --unsafe-mutable first")]
ImmutableFrostCheckout(u64),
#[error("tide has {count} open workitems; run `sirno tide status`{tutorial}")]
OpenTide {
count: usize,
tutorial: OpenTideTutorial,
},
#[error("frost version {0} is not a check-outable snapshot")]
InvalidFrostVersion(u64),
#[error("frost checkout requires `latest` or `version`")]
MissingFrostCheckoutTarget,
#[error("frost gc requires the current mutable lake; version {0} is checked out")]
FrostGcRequiresCurrentLake(u64),
#[error("artifact source has no file name: {0}")]
ArtifactSourceHasNoFileName(PathBuf),
#[error("move destination already exists: {0}")]
MoveDestinationExists(PathBuf),
#[error("failed to inspect move destination {path}")]
ReadMoveDestination {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to prepare move staging path near {path}")]
PrepareMoveStagingPath {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to create move destination parent {path}")]
CreateMoveDestinationParent {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to move {source_path} to {destination_path}")]
MovePath {
source_path: PathBuf,
destination_path: PathBuf,
#[source]
source: std::io::Error,
},
#[error(
"failed to move {source_path} to {destination_path}; rollback from {staging_path} failed: {rollback}"
)]
MovePathRollback {
source_path: PathBuf,
destination_path: PathBuf,
staging_path: PathBuf,
#[source]
source: std::io::Error,
rollback: std::io::Error,
},
#[error(
"failed to write config after moving {source_path} to {destination_path}; rollback failed: {rollback}"
)]
MoveConfigWriteRollback {
source_path: PathBuf,
destination_path: PathBuf,
#[source]
source: Box<ConfigError>,
rollback: std::io::Error,
},
#[error("repo members are not configured; add [repo].members to Sirno.toml")]
RepoMembersNotConfigured,
#[error("entry `{0}` does not exist")]
MissingWitnessEntry(EntryAddress),
#[error("`--lake-path` cannot be used with `check --frost-path`")]
LakePathWithFrostPath,
#[error("`--frost-path` only applies to `sirno check`")]
FrostPathRequiresCheck,
#[error("`--lake-path` cannot be used with `sirno util mcp`; configure the lake in Sirno.toml")]
McpRejectsLakePath,
#[error("`--frost-path` cannot be used with `sirno util mcp`; use `--config` only")]
McpRejectsFrostPath,
#[error("`--lake-path` cannot be used with `sirno util config`; use `--config` only")]
ConfigRejectsLakePath,
#[error("`--frost-path` cannot be used with `sirno util config`; use `--config` only")]
ConfigRejectsFrostPath,
#[error("terminal UI failed")]
TerminalUi(#[source] std::io::Error),
#[error("interactive init prompt failed")]
InteractiveInit(#[source] std::io::Error),
#[error("interactive init prompt reached end of input")]
InteractiveInitEof,
#[error("failed to create MCP runtime")]
CreateMcpRuntime(#[source] std::io::Error),
#[error("MCP server failed: {0}")]
McpServer(String),
#[error("`--lake-path` cannot be used with `sirno util skills`; wrappers are bundled")]
SkillsRejectsLakePath,
#[error("failed to read skill wrapper target {path}")]
ReadSkillWrapperTarget {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to create skill wrapper target directory {path}")]
CreateSkillWrapperTargetDirectory {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to write skill wrapper target {path}")]
WriteSkillWrapperTarget {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("skill wrapper target exists and is not a symlink: {0}")]
SkillWrapperTargetExists(PathBuf),
#[error("failed to remove skill wrapper target {path}")]
RemoveSkillWrapperTarget {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to link skill wrapper target {target_path} to {source_path}")]
LinkSkillWrapperTarget {
source_path: PathBuf,
target_path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("`--dry` only applies to `sirno render` without a subcommand")]
DryWithRenderSubcommand,
#[error("`--override-json` only applies to `sirno render` without a subcommand")]
OverrideJsonWithRenderSubcommand,
#[error("structural field `{0}` is not configured; add [structural.{0}] to Sirno.toml")]
UnconfiguredStructuralField(String),
#[error(
"generated-footer filtering cannot be combined with `rg --pre`; use `--with-generated-footer`"
)]
RgPreprocessorConflict,
#[error("rg generated-footer preprocessor expects one path argument")]
RgPreprocessorArgumentCount,
#[error("failed to locate current executable for rg preprocessor")]
LocateCurrentExe(#[source] std::io::Error),
#[error("failed to change current working directory to {path}")]
ChangeCurrentDirectory {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to locate current working directory")]
CurrentDirectory(#[source] std::io::Error),
#[error("failed to create rg preprocessor invoker at {path}")]
CreateRgPreprocessorInvoker {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to read rg preprocessor input {path}")]
ReadRgPreprocessorInput {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to write rg preprocessor output")]
WriteRgPreprocessorOutput(#[source] std::io::Error),
#[error(transparent)]
Config(#[from] ConfigError),
#[error(transparent)]
Lock(#[from] LockError),
#[error(transparent)]
Frost(#[from] FrostError),
#[error(transparent)]
Witness(#[from] WitnessError),
#[error(transparent)]
EntryDirectory(#[from] EntryDirectoryError),
#[error(transparent)]
EntryAddress(#[from] EntryAddressError),
#[error(transparent)]
EntryAtom(#[from] EntryAtomError),
#[error(transparent)]
ArtifactPath(#[from] EntryArtifactPathError),
#[error(transparent)]
EntryParse(#[from] EntryParseError),
#[error(transparent)]
GeneratedLink(#[from] GeneratedLinkError),
#[error(transparent)]
Tide(#[from] TideError),
#[error(transparent)]
Upstream(#[from] UpstreamError),
#[error("failed to run rg")]
RunRg(#[source] std::io::Error),
#[error("rg argument is not valid UTF-8: {0:?}")]
RgArgumentNotUtf8(OsString),
#[error(transparent)]
Json(#[from] serde_json::Error),
}