langcodec_cli/formats.rs
1use std::str::FromStr;
2
3/// Custom format types that are not supported by the lib crate.
4/// These are one-way conversions only (to Resource format).
5#[derive(Debug, Clone, PartialEq, clap::ValueEnum)]
6#[allow(clippy::enum_variant_names)]
7pub enum CustomFormat {
8 /// A JSON file which contains a map of language codes to translations.
9 ///
10 /// The key is the localization code, and the value is the translation:
11 ///
12 /// ```json
13 /// {
14 /// "key": "hello_world",
15 /// "en": "Hello, World!",
16 /// "fr": "Bonjour, le monde!"
17 /// }
18 /// ```
19 JSONLanguageMap,
20
21 /// A YAML file which contains a map of language codes to translations.
22 ///
23 /// The key is the localization code, and the value is the translation:
24 ///
25 /// ```yaml
26 /// key: hello_world
27 /// en: Hello, World!
28 /// fr: Bonjour, le monde!
29 /// ```
30 YAMLLanguageMap,
31
32 /// A JSON file which contains an array of language map objects.
33 ///
34 /// Each object contains a key and translations for different languages:
35 ///
36 /// ```json
37 /// [
38 /// {
39 /// "key": "hello_world",
40 /// "en": "Hello, World!",
41 /// "fr": "Bonjour, le monde!"
42 /// },
43 /// {
44 /// "key": "welcome_message",
45 /// "en": "Welcome to our app!",
46 /// "fr": "Bienvenue dans notre application!"
47 /// }
48 /// ]
49 /// ```
50 JSONArrayLanguageMap,
51}
52
53impl FromStr for CustomFormat {
54 type Err = String;
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 let normalized = s.trim().to_ascii_lowercase().replace(['-', '_'], "");
58 //: cspell:disable
59 match normalized.as_str() {
60 "jsonlanguagemap" => Ok(CustomFormat::JSONLanguageMap),
61 "jsonarraylanguagemap" => Ok(CustomFormat::JSONArrayLanguageMap),
62 "yamllanguagemap" => Ok(CustomFormat::YAMLLanguageMap),
63 // "csvlanguages" => Ok(CustomFormat::CSVLanguages),
64 _ => Err(format!(
65 "Unknown custom format: '{}'. Supported formats: json-language-map, json-array-language-map, yaml-language-map",
66 s
67 )),
68 }
69 //: cspell:enable
70 }
71}
72
73/// Parse a custom format from a string, with helpful error messages.
74pub fn parse_custom_format(s: &str) -> Result<CustomFormat, String> {
75 CustomFormat::from_str(s)
76}
77
78/// Detect if a file is a custom format based on its content and extension.
79/// Returns the detected custom format if found, None otherwise.
80pub fn detect_custom_format(file_path: &str, file_content: &str) -> Option<CustomFormat> {
81 let extension = std::path::Path::new(file_path)
82 .extension()
83 .and_then(|ext| ext.to_str())
84 .unwrap_or("")
85 .to_lowercase();
86
87 match extension.as_str() {
88 "json" => {
89 // Try to parse as JSON object first (JSONLanguageMap)
90 if serde_json::from_str::<serde_json::Value>(file_content).is_ok() {
91 // Check if it's an object (not an array)
92 if let Ok(obj) = serde_json::from_str::<
93 std::collections::HashMap<String, serde_json::Value>,
94 >(file_content)
95 {
96 if !obj.is_empty() {
97 return Some(CustomFormat::JSONLanguageMap);
98 }
99 }
100 // Check if it's an array (JSONArrayLanguageMap)
101 if serde_json::from_str::<Vec<serde_json::Value>>(file_content).is_ok() {
102 return Some(CustomFormat::JSONArrayLanguageMap);
103 }
104 }
105 }
106 "yaml" | "yml" => {
107 // Try to parse as YAML
108 if serde_yaml::from_str::<serde_yaml::Value>(file_content).is_ok() {
109 return Some(CustomFormat::YAMLLanguageMap);
110 }
111 }
112 _ => {}
113 }
114
115 None
116}
117
118/// Validate custom format file content
119pub fn validate_custom_format_content(
120 file_path: &str,
121 file_content: &str,
122) -> Result<CustomFormat, String> {
123 if file_content.trim().is_empty() {
124 return Err("File content is empty".to_string());
125 }
126
127 if let Some(format) = detect_custom_format(file_path, file_content) {
128 Ok(format)
129 } else {
130 Err(format!(
131 "Could not detect custom format from file content. Supported formats: {}",
132 get_supported_custom_formats()
133 ))
134 }
135}
136
137/// Get a list of all supported custom formats for help messages.
138pub fn get_supported_custom_formats() -> &'static str {
139 "json-language-map, json-array-language-map, yaml-language-map"
140}