git_z/command/
helpers.rs

1// git-z - A Git extension to go beyond.
2// Copyright (C) 2023-2024 Jean-Philippe Cugnet <jean-philippe@cugnet.eu>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3 of the License.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Helpers for writing CLIs.
17
18use std::{io, process::Command};
19
20use eyre::Result;
21use terminal_size::{Height, Width, terminal_size};
22use thiserror::Error;
23
24use crate::{
25    config::{CONFIG_FILE_NAME, Config, VERSION},
26    hint,
27    tracing::LogResult as _,
28    warning,
29};
30
31/// Errors that can occur when not inside a Git worktree.
32#[derive(Debug, Error)]
33pub enum NotInGitWorktree {
34    /// Git cannot be run.
35    #[error("Failed to run the git command")]
36    CannotRunGit(#[source] io::Error),
37    /// The command is not run from inside a Git repository.
38    #[error("Not in a Git repository")]
39    NotInRepo,
40    /// The command is not run from inside a Git worktree.
41    #[error("Not inside a Git worktree")]
42    NotInWorktree,
43}
44
45/// Ensures the command is run from a Git worktree.
46#[tracing::instrument(level = "trace")]
47pub fn ensure_in_git_worktree() -> Result<(), NotInGitWorktree> {
48    let is_inside_work_tree = Command::new("git")
49        .args(["rev-parse", "--is-inside-work-tree"])
50        .output()
51        .map_err(NotInGitWorktree::CannotRunGit)
52        .log_err()?;
53
54    if !is_inside_work_tree.status.success() {
55        return Err(NotInGitWorktree::NotInRepo).log_err();
56    }
57
58    if is_inside_work_tree.stdout == b"true\n" {
59        Ok(())
60    } else {
61        Err(NotInGitWorktree::NotInWorktree).log_err()
62    }
63}
64
65/// Loads the configuration.
66#[tracing::instrument(level = "trace")]
67pub fn load_config() -> Result<Config> {
68    let config = Config::load()?;
69
70    if config.version != VERSION {
71        warning!("The configuration in {CONFIG_FILE_NAME} is out of date.");
72        hint!("You can update it by running `git z update`.");
73    }
74
75    Ok(config)
76}
77
78/// Returns the page size for select prompts.
79pub fn page_size(previous_questions: usize) -> usize {
80    if let Some((Width(_), Height(term_height))) = terminal_size() {
81        usize::from(term_height) - 4 - previous_questions
82    } else {
83        15
84    }
85}
86
87/// Prints a success.
88#[macro_export]
89macro_rules! success {
90    ($($arg:tt)*) => {{
91        use colored::Colorize as _;
92        let message = indoc::formatdoc!($($arg)*).green().bold();
93        println!("{message}");
94    }};
95}
96
97/// Prints a warning.
98#[macro_export]
99macro_rules! warning {
100    ($($arg:tt)*) => {{
101        use colored::Colorize as _;
102
103        let log_message = $crate::helpers::uncapitalise(&format!($($arg)*));
104        let log_message = log_message.trim_end_matches(".");
105        tracing::warn!("{log_message}");
106
107        let message = indoc::formatdoc!($($arg)*).yellow().bold();
108        eprintln!("{message}");
109    }};
110}
111
112/// Prints an error.
113#[macro_export]
114macro_rules! error {
115    ($($arg:tt)*) => {{
116        use colored::Colorize as _;
117        let message = indoc::formatdoc!($($arg)*);
118        let message = $crate::helpers::uncapitalise(&message);
119        let message = format!("Error: {message}").red().bold();
120        eprintln!("{message}");
121    }};
122}
123
124/// Prints a hint.
125#[macro_export]
126macro_rules! hint {
127    ($($arg:tt)*) => {{
128        use colored::Colorize as _;
129        let message = indoc::formatdoc!($($arg)*).blue();
130        eprintln!("{message}");
131    }};
132}