tmux_backup/
config.rs

1//! Configuration.
2
3use std::env;
4use std::path::PathBuf;
5
6use clap::{ArgAction, Parser, Subcommand, ValueEnum, ValueHint};
7use clap_complete::Shell;
8
9use crate::management::{backup::BackupStatus, compaction::Strategy};
10
11/// Save or restore Tmux sessions.
12#[derive(Debug, Parser)]
13#[clap(author, about, version)]
14#[clap(propagate_version = true)]
15pub struct Config {
16    /// Location of backups.
17    ///
18    /// If unspecified, it falls back on: `$XDG_STATE_HOME/tmux-backup`, then on
19    /// `$HOME/.local/state/tmux-backup`.
20    #[arg(short = 'd', long = "dirpath", value_hint = ValueHint::DirPath,
21        default_value_os_t = default_backup_dirpath())]
22    pub backup_dirpath: PathBuf,
23
24    /// Selection of commands.
25    #[command(subcommand)]
26    pub command: Command,
27}
28
29/// Indicate whether to save (resp. restore) the Tmux sessions to (resp. from) a backup.
30#[derive(Debug, Subcommand)]
31pub enum Command {
32    /// Save the Tmux sessions to a new backup file.
33    ///
34    /// Sessions, windows, and panes geometry + content are saved in an archive format inside the
35    /// backup folder. In that folder, the backup name is expected to be similar to
36    /// `backup-20220531T123456.tar.zst`.
37    ///
38    /// If you run this command via a Tmux keybinding, use the `--to-tmux` flag in order to send a
39    /// one-line report to the Tmux status bar. If you run this command from the terminal, ignore
40    /// this flag in order to print the one-line report in the terminal.
41    Save {
42        /// Choose a strategy for managing backups.
43        #[command(flatten)]
44        strategy: StrategyConfig,
45
46        /// Print a one-line report in the Tmux status bar, otherwise print to stdout.
47        #[arg(long, action = ArgAction::SetTrue)]
48        to_tmux: bool,
49
50        /// Delete purgeable backups after saving.
51        #[arg(long, action = ArgAction::SetTrue)]
52        compact: bool,
53
54        /// Number of lines to ignore during capture if the active command is a shell.
55        ///
56        /// At the time of saving, for each pane where the active command is one of (`zsh`, `bash`,
57        /// `fish`), the shell prompt is waiting for input. If tmux-backup naively captures the
58        /// entire history, on restoring that backup, a new shell prompt will also appear. This
59        /// obviously pollutes history with repeated shell prompts.
60        ///
61        /// If you know the number of lines your shell prompt occupies on screen, set this option
62        /// to that number (simply `1` in my case). These last lines will not be captured. On
63        /// restore, this gives the illusion of history continuity without repetition.
64        #[arg(
65            short = 'i',
66            long = "ignore-last-lines",
67            value_name = "NUMBER",
68            default_value_t = 0
69        )]
70        num_lines_to_drop: u8,
71    },
72
73    /// Restore the Tmux sessions from a backup file.
74    ///
75    /// Sessions, windows and panes geometry + content are read from the backup marked as "current"
76    /// (often the most recent backup) inside the backup folder. In that folder, the backup name is
77    /// expected to be similar to `backup-20220531T123456.tar.zst`.
78    ///
79    /// If you run this command via a Tmux keybinding, use the `--to-tmux` flag in order to send a
80    /// one-line report to the Tmux status bar. If you run this command from the terminal, ignore
81    /// this flag in order to print the one-line report in the terminal.
82    Restore {
83        /// Choose a strategy for managing backups.
84        #[command(flatten)]
85        strategy: StrategyConfig,
86
87        /// Print a one-line report in the Tmux status bar, otherwise print to stdout.
88        #[arg(long, action = ArgAction::SetTrue)]
89        to_tmux: bool,
90
91        /// Filepath of the backup to restore, by default, pick latest.
92        #[arg(value_parser)]
93        backup_filepath: Option<PathBuf>,
94    },
95
96    /// Catalog commands.
97    Catalog {
98        /// Choose a strategy for managing backups.
99        #[command(flatten)]
100        strategy: StrategyConfig,
101
102        /// Catalog commands.
103        #[command(subcommand)]
104        command: CatalogSubcommand,
105    },
106
107    /// Describe the content of a backup file.
108    Describe {
109        /// Path to the backup file.
110        #[arg(value_parser, value_hint = ValueHint::FilePath)]
111        backup_filepath: PathBuf,
112    },
113
114    /// Print a shell completion script to stdout.
115    GenerateCompletion {
116        /// Shell for which you want completion.
117        #[arg(value_enum, value_parser = clap::value_parser!(Shell))]
118        shell: Shell,
119    },
120
121    /// Outputs the default tmux plugin config to stdout.
122    ///
123    /// Similar to shell completions, this is done once when installing tmux-backup. Type
124    /// `tmux-backup init > ~/.tmux/plugins/tmux-backup.tmux`. and source it
125    /// from your `~/.tmux.conf`. See the README for details.
126    Init,
127}
128
129/// Catalog subcommands.
130#[derive(Debug, Subcommand)]
131pub enum CatalogSubcommand {
132    /// Print a list of backups to stdout.
133    ///
134    /// By default, this prints a table of backups, age and status with colors. The flag `--details`
135    /// prints additional columns.
136    ///
137    /// If the flag `--filepaths` is set, only absolute filepaths are printed. This can be used in
138    /// scripting scenarios.
139    ///
140    /// Options `--only purgeable` or `--only retainable` will list only the corresponding backups.
141    /// They will activate the flag `--filepaths` automatically.
142    List {
143        /// Add details columns to the table.
144        ///
145        /// Print number of sessions, windows and panes in the backup and the backup's format
146        /// version. This is slightly slower because it requires each backup file to be partially
147        /// read.
148        #[arg(long = "details", action = ArgAction::SetTrue)]
149        details_flag: bool,
150
151        /// List only backups having this status.
152        #[arg(long = "only", value_enum, value_parser)]
153        only_backup_status: Option<BackupStatus>,
154
155        /// Print filepaths instead of the table format.
156        #[arg(long = "filepaths", action = ArgAction::SetTrue)]
157        filepaths_flag: bool,
158    },
159
160    /// Apply the catalog's compaction strategy: this deletes all purgable backups.
161    Compact,
162}
163
164/// Strategy values
165#[derive(Debug, Clone, ValueEnum)]
166enum StrategyValues {
167    /// Apply a most-recent strategy, keeping only n backups.
168    MostRecent,
169
170    /// Apply a classic backup strategy.
171    ///
172    /// Keep
173    /// the lastest per hour for the past 24 hours,
174    /// the lastest per day for the past 7 days,
175    /// the lastest per week of the past 4 weeks,
176    /// the lastest per month of this year.
177    Classic,
178}
179
180/// Strategy configuration.
181#[derive(Debug, clap::Args)]
182pub struct StrategyConfig {
183    #[arg(short = 's', long = "strategy", value_enum, default_value_t = StrategyValues::MostRecent)]
184    strategy: StrategyValues,
185
186    /// Number of recent backups to keep, for instance 10.
187    #[arg(
188        short = 'n',
189        long,
190        value_name = "NUMBER",
191        value_parser = clap::value_parser!(u16).range(1..),
192        default_value_t = 10,
193    )]
194    num_backups: u16,
195}
196
197//
198// Helpers
199//
200
201impl StrategyConfig {
202    /// Compaction Strategy corresponding to the CLI arguments.
203    pub fn strategy(&self) -> Strategy {
204        match self.strategy {
205            StrategyValues::MostRecent => Strategy::most_recent(self.num_backups as usize),
206            StrategyValues::Classic => Strategy::Classic,
207        }
208    }
209}
210
211/// Determine the folder where to save backups.
212///
213/// If `$XDG_STATE_HOME` is defined, the function returns `$XDG_STATE_HOME/tmux-backup`, otherwise,
214/// it returns `$HOME/.local/state/tmux-backup`.
215///
216/// # Panics
217///
218/// This function panics if even `$HOME` cannot be obtained from the environment.
219fn default_backup_dirpath() -> PathBuf {
220    let state_home = match env::var("XDG_STATE_HOME") {
221        Ok(v) => PathBuf::from(v),
222        Err(_) => match env::var("HOME") {
223            Ok(v) => PathBuf::from(v).join(".local").join("state"),
224            Err(_) => panic!("Cannot find `$HOME` in the environment"),
225        },
226    };
227
228    state_home.join("tmux-backup")
229}