agpm_cli/utils/mod.rs
1//! Cross-platform utilities and helpers
2//!
3//! This module provides utility functions for file operations, platform-specific
4//! code, and user interface elements like progress bars. All utilities are designed
5//! to work consistently across Windows, macOS, and Linux.
6//!
7//! # Modules
8//!
9//! - [`fs`] - File system operations with atomic writes and safe copying
10//! - [`manifest_utils`] - Utilities for loading and validating manifests
11//! - [`platform`] - Platform-specific helpers and path resolution
12//! - [`progress`] - Multi-phase progress tracking for long-running operations
13//!
14//! # Cross-Platform Considerations
15//!
16//! All utilities handle platform differences:
17//! - Path separators (`/` vs `\`)
18//! - Line endings (`\n` vs `\r\n`)
19//! - File permissions and attributes
20//! - Shell commands and environment variables
21//!
22//! # Example
23//!
24//! ```rust,no_run
25//! use agpm_cli::utils::{ensure_dir, atomic_write, MultiPhaseProgress, InstallationPhase};
26//! use std::path::Path;
27//!
28//! # async fn example() -> anyhow::Result<()> {
29//! // Ensure directory exists
30//! ensure_dir(Path::new("output/agents"))?;
31//!
32//! // Write file atomically
33//! atomic_write(Path::new("output/config.toml"), b"content")?;
34//!
35//! // Show progress with phases
36//! let progress = MultiPhaseProgress::new(true);
37//! progress.start_phase(InstallationPhase::Installing, Some("Processing files"));
38//! # Ok(())
39//! # }
40//! ```
41
42pub mod fs;
43pub mod manifest_utils;
44pub mod path_validation;
45pub mod platform;
46pub mod progress;
47pub mod security;
48
49pub use fs::{
50 atomic_write, compare_file_times, copy_dir, create_temp_file, ensure_dir,
51 file_exists_and_readable, get_modified_time, normalize_path, read_json_file, read_text_file,
52 read_toml_file, read_yaml_file, safe_write, write_json_file, write_text_file, write_toml_file,
53 write_yaml_file,
54};
55pub use manifest_utils::{
56 load_and_validate_manifest, load_project_manifest, manifest_exists, manifest_path,
57};
58pub use path_validation::{
59 ensure_directory_exists, ensure_within_directory, find_project_root, safe_canonicalize,
60 safe_relative_path, sanitize_file_name, validate_no_traversal, validate_project_path,
61 validate_resource_path,
62};
63pub use platform::{get_git_command, get_home_dir, is_windows, resolve_path};
64pub use progress::{InstallationPhase, MultiPhaseProgress, ProgressBar, collect_dependency_names};
65
66/// Determines if a given URL/path is a local filesystem path (not a Git repository URL).
67///
68/// Local paths are directories on the filesystem that are directly accessible,
69/// as opposed to Git repository URLs that need to be cloned/fetched.
70///
71/// # Examples
72///
73/// ```
74/// use agpm_cli::utils::is_local_path;
75///
76/// // Unix-style paths
77/// assert!(is_local_path("/absolute/path"));
78/// assert!(is_local_path("./relative/path"));
79/// assert!(is_local_path("../parent/path"));
80///
81/// // Windows-style paths (with drive letters or UNC)
82/// assert!(is_local_path("C:/Users/path"));
83/// assert!(is_local_path("C:\\Users\\path"));
84/// assert!(is_local_path("//server/share"));
85/// assert!(is_local_path("\\\\server\\share"));
86///
87/// // Git URLs (not local paths)
88/// assert!(!is_local_path("https://github.com/user/repo.git"));
89/// assert!(!is_local_path("git@github.com:user/repo.git"));
90/// assert!(!is_local_path("file:///path/to/repo.git"));
91/// ```
92#[must_use]
93pub fn is_local_path(url: &str) -> bool {
94 // file:// URLs are Git repository URLs, not local paths
95 if url.starts_with("file://") {
96 return false;
97 }
98
99 // Unix-style absolute or relative paths
100 if url.starts_with('/') || url.starts_with("./") || url.starts_with("../") {
101 return true;
102 }
103
104 // Windows-style paths
105 // Check for drive letter (e.g., C:/ or C:\)
106 if url.len() >= 2 {
107 let chars: Vec<char> = url.chars().collect();
108 if chars[0].is_ascii_alphabetic() && chars[1] == ':' {
109 return true;
110 }
111 }
112
113 // Check for UNC paths (e.g., //server/share or \\server\share)
114 if url.starts_with("//") || url.starts_with("\\\\") {
115 return true;
116 }
117
118 false
119}
120
121/// Determines if a given URL is a Git repository URL (including file:// URLs).
122///
123/// Git repository URLs need to be cloned/fetched, unlike local filesystem paths.
124///
125/// # Examples
126///
127/// ```
128/// use agpm_cli::utils::is_git_url;
129///
130/// assert!(is_git_url("https://github.com/user/repo.git"));
131/// assert!(is_git_url("git@github.com:user/repo.git"));
132/// assert!(is_git_url("file:///path/to/repo.git"));
133/// assert!(is_git_url("ssh://git@server.com/repo.git"));
134/// assert!(!is_git_url("/absolute/path"));
135/// assert!(!is_git_url("./relative/path"));
136/// ```
137#[must_use]
138pub fn is_git_url(url: &str) -> bool {
139 !is_local_path(url)
140}