1use crate::error::{RecError, Result};
4use crate::models::session::validate_session_name;
5use std::fmt;
6use std::path::Path;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ImportFormat {
11 BashScript,
12 BashHistory,
13 ZshHistory,
14 FishHistory,
15}
16
17impl fmt::Display for ImportFormat {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 ImportFormat::BashScript => write!(f, "bash-script"),
21 ImportFormat::BashHistory => write!(f, "bash-history"),
22 ImportFormat::ZshHistory => write!(f, "zsh-history"),
23 ImportFormat::FishHistory => write!(f, "fish-history"),
24 }
25 }
26}
27
28pub fn detect_format(path: &str, content: &str) -> Result<ImportFormat> {
46 use std::sync::OnceLock;
47 static ZSH_RE: OnceLock<regex::Regex> = OnceLock::new();
48 let path = Path::new(path);
49
50 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
52 match ext {
53 "cast" => {
54 return Err(RecError::InvalidSession(
55 "Asciinema .cast files are not currently supported. \
56 Use bash scripts or shell history files instead."
57 .to_string(),
58 ));
59 }
60 "sh" | "bash" => return Ok(ImportFormat::BashScript),
61 _ => {}
62 }
63 }
64
65 let first_lines: Vec<&str> = content.lines().take(10).collect();
67
68 if let Some(first) = first_lines.first() {
70 if first.starts_with("#!") && (first.contains("bash") || first.contains("/sh")) {
71 return Ok(ImportFormat::BashScript);
72 }
73 }
74
75 let zsh_re = ZSH_RE.get_or_init(|| regex::Regex::new(r"^: \d+:\d+;").unwrap());
77 for line in &first_lines {
78 if zsh_re.is_match(line) {
79 return Ok(ImportFormat::ZshHistory);
80 }
81 }
82
83 for line in &first_lines {
85 if line.starts_with("- cmd: ") {
86 return Ok(ImportFormat::FishHistory);
87 }
88 }
89
90 Ok(ImportFormat::BashHistory)
92}
93
94#[must_use]
105pub fn session_name_from_path(path: &Path) -> String {
106 let filename = path
107 .file_name()
108 .and_then(|n| n.to_str())
109 .unwrap_or("imported");
110
111 let filename = filename.strip_prefix('.').unwrap_or(filename);
113
114 let sanitized: String = filename
116 .chars()
117 .map(|c| {
118 if c.is_alphanumeric() || c == '-' || c == '_' {
119 c
120 } else {
121 '-'
122 }
123 })
124 .collect();
125
126 let mut result = String::new();
128 let mut prev_hyphen = false;
129 for c in sanitized.chars() {
130 if c == '-' {
131 if !prev_hyphen {
132 result.push(c);
133 }
134 prev_hyphen = true;
135 } else {
136 result.push(c);
137 prev_hyphen = false;
138 }
139 }
140
141 let result = result.trim_matches('-').to_lowercase();
143
144 if result.is_empty() || validate_session_name(&result).is_err() {
146 return "imported-session".to_string();
147 }
148
149 result
150}