Skip to main content

fraisier_ship/
lib.rs

1//! Version management and the release workflow for the `fraisier` CLI.
2//!
3//! This crate backs `fraisier version show` / `version bump` and (the
4//! [`ship`](crate::ship) workflow) `fraisier ship`. The version logic understands
5//! both Rust (`Cargo.toml`) and Python (`pyproject.toml`) projects, editing the
6//! file in place with `toml_edit` so formatting and comments survive a bump.
7
8use std::path::{Path, PathBuf};
9
10mod ship;
11mod version;
12
13pub use ship::{ship, ShipOptions, ShipReport};
14pub use version::{bump, locate, next_version, show, Bump, ProjectKind, VersionInfo};
15
16/// An error from reading or bumping a project's version.
17#[derive(Debug, thiserror::Error)]
18pub enum ShipError {
19    /// Neither `Cargo.toml` nor `pyproject.toml` was found in the directory.
20    #[error("no version file in {0} (looked for Cargo.toml, pyproject.toml)")]
21    NoVersionFile(PathBuf),
22    /// The version file was found but carries no version field.
23    #[error("{path}: no version field found ({detail})")]
24    NoVersionField {
25        /// The file inspected.
26        path: PathBuf,
27        /// Which tables were checked.
28        detail: String,
29    },
30    /// The version file could not be parsed as TOML.
31    #[error("{path}: invalid TOML: {source}")]
32    Toml {
33        /// The file that failed to parse.
34        path: PathBuf,
35        /// The underlying parse error.
36        source: toml_edit::TomlError,
37    },
38    /// The current version string is not valid semver.
39    #[error("{version:?} is not a valid semantic version: {source}")]
40    Semver {
41        /// The offending version string.
42        version: String,
43        /// The underlying semver parse error.
44        source: semver::Error,
45    },
46    /// Reading the version file failed.
47    #[error("reading {path}: {source}")]
48    Read {
49        /// The file that could not be read.
50        path: PathBuf,
51        /// The underlying I/O error.
52        source: std::io::Error,
53    },
54    /// Writing the version file failed.
55    #[error("writing {path}: {source}")]
56    Write {
57        /// The file that could not be written.
58        path: PathBuf,
59        /// The underlying I/O error.
60        source: std::io::Error,
61    },
62    /// `ship` refuses to run with uncommitted changes in the working tree.
63    #[error("the working tree at {dir} has uncommitted changes; commit or stash them before ship")]
64    DirtyWorkingTree {
65        /// The repository directory.
66        dir: PathBuf,
67    },
68    /// A `git` invocation failed (non-zero exit or could not be spawned).
69    #[error("git {op} failed: {detail}")]
70    Git {
71        /// The git operation that failed (e.g. `commit`).
72        op: String,
73        /// The captured stderr or spawn error.
74        detail: String,
75    },
76}
77
78/// Read `path` to a string, mapping I/O failure to [`ShipError::Read`].
79pub(crate) fn read(path: &Path) -> Result<String, ShipError> {
80    std::fs::read_to_string(path).map_err(|source| ShipError::Read {
81        path: path.to_path_buf(),
82        source,
83    })
84}
85
86/// Write `contents` to `path`, mapping I/O failure to [`ShipError::Write`].
87pub(crate) fn write(path: &Path, contents: &str) -> Result<(), ShipError> {
88    std::fs::write(path, contents).map_err(|source| ShipError::Write {
89        path: path.to_path_buf(),
90        source,
91    })
92}