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}