smirrors 0.1.0

Automatic mirror list updater for Linux distributions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//! Command-line argument parsing for SMirrors
//!
//! This module defines the complete CLI interface using clap derive macros.

use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;

/// SMirrors - Automatic mirror list updater for Linux distributions
///
/// SMirrors automatically tests and updates package manager mirror lists
/// based on download speed and latency, ensuring optimal package download
/// performance for your system.
#[derive(Parser, Debug)]
#[command(name = "smirrors")]
#[command(about = "Automatic mirror list updater for Linux distributions", long_about = None)]
#[command(version)]
#[command(author)]
pub struct Cli {
    /// Command to execute
    #[command(subcommand)]
    pub command: Commands,

    /// Enable verbose output
    #[arg(short, long, global = true)]
    pub verbose: bool,

    /// Path to configuration file
    #[arg(short, long, global = true, value_name = "FILE")]
    pub config: Option<PathBuf>,

    /// Suppress all non-error output
    #[arg(short, long, global = true, conflicts_with = "verbose")]
    pub quiet: bool,
}

/// Available commands for SMirrors
#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Test mirrors without updating
    ///
    /// Performs speed and latency tests on available mirrors without
    /// modifying your system's configuration. Useful for previewing
    /// which mirrors would be selected.
    Test {
        /// Number of mirrors to test
        #[arg(short = 'n', long, value_name = "COUNT")]
        count: Option<usize>,

        /// Output format
        #[arg(short, long, value_enum, default_value = "table")]
        format: OutputFormat,

        /// Show only successful tests
        #[arg(short, long)]
        success_only: bool,

        /// Sort by score (default), speed, or latency
        #[arg(short = 'S', long, value_enum, default_value = "score")]
        sort: SortBy,
    },

    /// Update mirror list
    ///
    /// Tests available mirrors and updates your system's package manager
    /// configuration with the fastest mirrors. Creates a backup before
    /// making changes.
    Update {
        /// Perform a dry run without making changes
        #[arg(long)]
        dry_run: bool,

        /// Force update even if recently updated
        #[arg(short, long)]
        force: bool,

        /// Limit number of mirrors to test (for testing/debugging)
        #[arg(short = 'n', long, value_name = "COUNT")]
        limit: Option<usize>,

        /// Skip confirmation prompt
        #[arg(short = 'y', long)]
        yes: bool,
    },

    /// List current mirrors
    ///
    /// Display mirrors currently configured in your system's package
    /// manager configuration.
    List {
        /// Show static mirrors only
        #[arg(long)]
        static_only: bool,

        /// Include test results if available
        #[arg(short = 't', long)]
        with_tests: bool,

        /// Output format
        #[arg(short, long, value_enum, default_value = "table")]
        format: OutputFormat,
    },

    /// Add a static mirror
    ///
    /// Static mirrors are preserved during updates and always included
    /// in the mirror list regardless of their test results.
    Add {
        /// Repository name/identifier
        #[arg(short, long, value_name = "NAME")]
        repo: Option<String>,

        /// Mirror URL
        #[arg(value_name = "URL")]
        url: String,

        /// Skip URL validation
        #[arg(long)]
        skip_validation: bool,
    },

    /// Remove a mirror
    ///
    /// Remove a static mirror from the configuration. Does not affect
    /// dynamically selected mirrors.
    Remove {
        /// Mirror URL or alias to remove
        #[arg(value_name = "MIRROR")]
        mirror: String,

        /// Skip confirmation prompt
        #[arg(short = 'y', long)]
        yes: bool,
    },

    /// Launch interactive TUI
    ///
    /// Opens an interactive terminal user interface for managing mirrors,
    /// viewing test results, and configuring settings.
    Tui,

    /// Show service status
    ///
    /// Display the status of the SMirrors systemd service and timer,
    /// including last update time and next scheduled update.
    Status {
        /// Show detailed status information
        #[arg(short, long)]
        detailed: bool,

        /// Output format
        #[arg(short, long, value_enum, default_value = "pretty")]
        format: OutputFormat,
    },

    /// View update history
    ///
    /// Display history of mirror updates including timestamps,
    /// number of mirrors changed, and success/failure status.
    History {
        /// Number of entries to show
        #[arg(short = 'n', long, default_value = "10", value_name = "COUNT")]
        count: usize,

        /// Output format
        #[arg(short, long, value_enum, default_value = "table")]
        format: OutputFormat,

        /// Show only successful updates
        #[arg(short, long)]
        success_only: bool,

        /// Show only failed updates
        #[arg(short = 'F', long, conflicts_with = "success_only")]
        failed_only: bool,
    },

    /// Rollback to previous configuration
    ///
    /// Restore the most recent backup of your mirror configuration.
    /// This will undo the last update operation.
    Rollback {
        /// Backup ID to restore (defaults to most recent)
        #[arg(value_name = "BACKUP_ID")]
        backup_id: Option<String>,

        /// Skip confirmation prompt
        #[arg(short = 'y', long)]
        yes: bool,

        /// List available backups instead of rolling back
        #[arg(short, long)]
        list: bool,
    },

    /// Enable automatic updates
    ///
    /// Enables the SMirrors systemd timer to automatically update
    /// mirrors at the configured interval.
    Enable {
        /// Also start the service immediately
        #[arg(short, long)]
        now: bool,
    },

    /// Disable automatic updates
    ///
    /// Disables the SMirrors systemd timer to prevent automatic updates.
    /// Manual updates can still be performed.
    Disable {
        /// Also stop the currently running service
        #[arg(short, long)]
        stop: bool,
    },

    /// Manage configuration
    ///
    /// View, edit, or modify SMirrors configuration settings.
    Config {
        /// Configuration action to perform
        #[command(subcommand)]
        action: Option<ConfigAction>,
    },

    /// Initialize SMirrors
    ///
    /// Set up SMirrors for first-time use, creating configuration
    /// files and directories with default settings.
    Init {
        /// Force reinitialization even if already set up
        #[arg(short, long)]
        force: bool,

        /// Skip systemd service installation
        #[arg(long)]
        skip_service: bool,
    },

    /// Run as a background service (used by systemd)
    #[command(hide = true)]
    Service {
        #[command(subcommand)]
        action: ServiceAction,
    },
}

/// Configuration management actions
#[derive(Subcommand, Debug)]
pub enum ConfigAction {
    /// Show current configuration
    ///
    /// Display the current configuration in human-readable format
    /// or as raw TOML.
    Show {
        /// Show as raw TOML
        #[arg(short, long)]
        raw: bool,

        /// Show specific section only
        #[arg(short, long, value_name = "SECTION")]
        section: Option<String>,
    },

    /// Set a configuration value
    ///
    /// Modify a configuration setting using key=value format.
    /// Key should be in the form "section.key" (e.g., "general.timeout").
    Set {
        /// Configuration key (section.key format)
        #[arg(value_name = "KEY")]
        key: String,

        /// Configuration value
        #[arg(value_name = "VALUE")]
        value: String,
    },

    /// Get a configuration value
    ///
    /// Retrieve the current value of a specific configuration setting.
    Get {
        /// Configuration key (section.key format)
        #[arg(value_name = "KEY")]
        key: String,
    },

    /// Edit configuration file
    ///
    /// Open the configuration file in your default editor (from $EDITOR).
    Edit {
        /// Validate configuration after editing
        #[arg(short, long, default_value = "true")]
        validate: bool,
    },

    /// Validate configuration
    ///
    /// Check configuration file for errors without making changes.
    Validate {
        /// Show detailed validation output
        #[arg(short, long)]
        verbose: bool,
    },

    /// Reset configuration to defaults
    ///
    /// Restore default configuration settings. Can reset specific
    /// sections or the entire configuration.
    Reset {
        /// Section to reset (or all if not specified)
        #[arg(value_name = "SECTION")]
        section: Option<String>,

        /// Skip confirmation prompt
        #[arg(short = 'y', long)]
        yes: bool,
    },
}

/// Service management actions (hidden from normal users)
#[derive(Subcommand, Debug)]
pub enum ServiceAction {
    /// Run the service in foreground
    Run,

    /// Perform a single update and exit
    Update,
}

/// Output format options
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum OutputFormat {
    /// Human-readable table format
    Table,
    /// JSON format for machine parsing
    Json,
    /// Pretty-printed format
    Pretty,
    /// Compact single-line format
    Compact,
}

/// Sort criteria for mirror listings
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum SortBy {
    /// Sort by overall score
    Score,
    /// Sort by download speed
    Speed,
    /// Sort by latency
    Latency,
    /// Sort by URL
    Url,
}

impl Cli {
    /// Parse command-line arguments
    pub fn parse_args() -> Self {
        Self::parse()
    }

    /// Get log level based on verbosity flags
    pub fn log_level(&self) -> &str {
        if self.quiet {
            "error"
        } else if self.verbose {
            "debug"
        } else {
            "info"
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cli_parsing_test_command() {
        let cli = Cli::parse_from(["smirrors", "test", "--count", "5"]);

        match cli.command {
            Commands::Test { count, .. } => {
                assert_eq!(count, Some(5));
            }
            _ => panic!("Expected Test command"),
        }
    }

    #[test]
    fn test_cli_parsing_update_command() {
        let cli = Cli::parse_from(["smirrors", "update", "--dry-run"]);

        match cli.command {
            Commands::Update { dry_run, .. } => {
                assert!(dry_run);
            }
            _ => panic!("Expected Update command"),
        }
    }

    #[test]
    fn test_cli_parsing_config_set() {
        let cli = Cli::parse_from(["smirrors", "config", "set", "general.timeout", "15"]);

        match cli.command {
            Commands::Config {
                action: Some(ConfigAction::Set { key, value }),
            } => {
                assert_eq!(key, "general.timeout");
                assert_eq!(value, "15");
            }
            _ => panic!("Expected Config Set command"),
        }
    }

    #[test]
    fn test_log_level_quiet() {
        let cli = Cli::parse_from(["smirrors", "--quiet", "status"]);
        assert_eq!(cli.log_level(), "error");
    }

    #[test]
    fn test_log_level_verbose() {
        let cli = Cli::parse_from(["smirrors", "--verbose", "status"]);
        assert_eq!(cli.log_level(), "debug");
    }

    #[test]
    fn test_log_level_default() {
        let cli = Cli::parse_from(["smirrors", "status"]);
        assert_eq!(cli.log_level(), "info");
    }
}