@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
44pub enum FormatKind {
55 Ramac ,
66 PulseEkko ,
7+ Gssi ,
78}
89
910#[ derive( Debug , Clone , serde:: Serialize ) ]
@@ -64,6 +65,19 @@ pub fn all_formats() -> Vec<FormatInfo> {
6465 coordinates: ".gp2" ,
6566 } ,
6667 } ,
68+ FormatInfo {
69+ name: "gssi" ,
70+ description: "GSSI DZT/DZG format" ,
71+ capabilities: FormatCapabilities {
72+ read: true ,
73+ write: false ,
74+ } ,
75+ files: FormatFiles {
76+ header: ".DZT" ,
77+ data: ".DZT" ,
78+ coordinates: ".DZG" ,
79+ } ,
80+ } ,
6781 ]
6882}
6983
@@ -77,9 +91,37 @@ pub fn format_info(kind: FormatKind) -> FormatInfo {
7791 . into_iter ( )
7892 . find ( |fmt| fmt. name == "pulseekko" )
7993 . unwrap ( ) ,
94+ FormatKind :: Gssi => all_formats ( )
95+ . into_iter ( )
96+ . find ( |fmt| fmt. name == "gssi" )
97+ . unwrap ( ) ,
8098 }
8199}
82100
101+ pub fn find_neighbor_case_insensitive ( base : & Path , target_extension : & str ) -> Option < PathBuf > {
102+ let parent = base. parent ( ) . unwrap_or_else ( || Path :: new ( "." ) ) ;
103+ let stem = base. file_stem ( ) . or_else ( || base. file_name ( ) ) ?. to_str ( ) ?;
104+ let target_extension = target_extension. trim_start_matches ( '.' ) ;
105+
106+ std:: fs:: read_dir ( parent)
107+ . ok ( ) ?
108+ . filter_map ( |entry| entry. ok ( ) . map ( |entry| entry. path ( ) ) )
109+ . find ( |path| {
110+ path. file_stem ( )
111+ . and_then ( |s| s. to_str ( ) )
112+ . is_some_and ( |s| s. eq_ignore_ascii_case ( stem) )
113+ && path
114+ . extension ( )
115+ . and_then ( |s| s. to_str ( ) )
116+ . is_some_and ( |ext| ext. eq_ignore_ascii_case ( target_extension) )
117+ } )
118+ }
119+
120+ fn resolve_sidecar ( base : & Path , target_extension : & str ) -> PathBuf {
121+ find_neighbor_case_insensitive ( base, target_extension)
122+ . unwrap_or_else ( || base. with_extension ( target_extension) )
123+ }
124+
83125pub fn resolve_input ( input : & Path ) -> Result < ResolvedInput , String > {
84126 let ext = input
85127 . extension ( )
@@ -90,60 +132,90 @@ pub fn resolve_input(input: &Path) -> Result<ResolvedInput, String> {
90132 Some ( "rad" ) => Ok ( ResolvedInput {
91133 input : input. to_path_buf ( ) ,
92134 kind : FormatKind :: Ramac ,
93- header : input . to_path_buf ( ) ,
94- data : input . with_extension ( "rd3" ) ,
95- coordinates : input . with_extension ( "cor" ) ,
135+ header : resolve_sidecar ( input , "rad" ) ,
136+ data : resolve_sidecar ( input , "rd3" ) ,
137+ coordinates : resolve_sidecar ( input , "cor" ) ,
96138 } ) ,
97139 Some ( "rd3" ) | Some ( "cor" ) => Ok ( ResolvedInput {
98140 input : input. to_path_buf ( ) ,
99141 kind : FormatKind :: Ramac ,
100- header : input . with_extension ( "rad" ) ,
101- data : input . with_extension ( "rd3" ) ,
102- coordinates : input . with_extension ( "cor" ) ,
142+ header : resolve_sidecar ( input , "rad" ) ,
143+ data : resolve_sidecar ( input , "rd3" ) ,
144+ coordinates : resolve_sidecar ( input , "cor" ) ,
103145 } ) ,
104146 Some ( "hd" ) => Ok ( ResolvedInput {
105147 input : input. to_path_buf ( ) ,
106148 kind : FormatKind :: PulseEkko ,
107- header : input . to_path_buf ( ) ,
108- data : input . with_extension ( "dt1" ) ,
109- coordinates : input . with_extension ( "gp2" ) ,
149+ header : resolve_sidecar ( input , "hd" ) ,
150+ data : resolve_sidecar ( input , "dt1" ) ,
151+ coordinates : resolve_sidecar ( input , "gp2" ) ,
110152 } ) ,
111153 Some ( "dt1" ) | Some ( "gp2" ) => Ok ( ResolvedInput {
112154 input : input. to_path_buf ( ) ,
113155 kind : FormatKind :: PulseEkko ,
114- header : input. with_extension ( "hd" ) ,
115- data : input. with_extension ( "dt1" ) ,
116- coordinates : input. with_extension ( "gp2" ) ,
156+ header : resolve_sidecar ( input, "hd" ) ,
157+ data : resolve_sidecar ( input, "dt1" ) ,
158+ coordinates : resolve_sidecar ( input, "gp2" ) ,
159+ } ) ,
160+ Some ( "dzt" ) | Some ( "dzg" ) | Some ( "dzx" ) => Ok ( ResolvedInput {
161+ input : input. to_path_buf ( ) ,
162+ kind : FormatKind :: Gssi ,
163+ header : resolve_sidecar ( input, "dzt" ) ,
164+ data : resolve_sidecar ( input, "dzt" ) ,
165+ coordinates : resolve_sidecar ( input, "dzg" ) ,
117166 } ) ,
118167 Some ( other) => Err ( format ! (
119- "Unsupported input extension '.{other}' for {:?}. Supported formats are RAMAC (.rad/.rd3/.cor) and pulseEKKO (.hd/.dt1/.gp2)." ,
168+ "Unsupported input extension '.{other}' for {:?}. Supported formats are RAMAC (.rad/.rd3/.cor), pulseEKKO (.hd/.dt1/.gp2), and GSSI (.dzt/.dzg/.dzx )." ,
120169 input
121170 ) ) ,
122171 None => {
123- let ramac = input. with_extension ( "rad" ) ;
124- let pulseekko = input. with_extension ( "hd" ) ;
125- match ( ramac. is_file ( ) , pulseekko. is_file ( ) ) {
126- ( true , false ) => Ok ( ResolvedInput {
172+ let ramac = find_neighbor_case_insensitive ( input, "rad" ) ;
173+ let pulseekko = find_neighbor_case_insensitive ( input, "hd" ) ;
174+ let gssi = find_neighbor_case_insensitive ( input, "dzt" ) ;
175+ match ( ramac, pulseekko, gssi) {
176+ ( Some ( ramac) , None , None ) => Ok ( ResolvedInput {
127177 input : input. to_path_buf ( ) ,
128178 kind : FormatKind :: Ramac ,
129179 header : ramac. clone ( ) ,
130- data : ramac . with_extension ( "rd3" ) ,
131- coordinates : ramac . with_extension ( "cor" ) ,
180+ data : resolve_sidecar ( & ramac , "rd3" ) ,
181+ coordinates : resolve_sidecar ( & ramac , "cor" ) ,
132182 } ) ,
133- ( false , true ) => Ok ( ResolvedInput {
183+ ( None , Some ( pulseekko ) , None ) => Ok ( ResolvedInput {
134184 input : input. to_path_buf ( ) ,
135185 kind : FormatKind :: PulseEkko ,
136186 header : pulseekko. clone ( ) ,
137- data : pulseekko. with_extension ( "dt1" ) ,
138- coordinates : pulseekko. with_extension ( "gp2" ) ,
187+ data : resolve_sidecar ( & pulseekko, "dt1" ) ,
188+ coordinates : resolve_sidecar ( & pulseekko, "gp2" ) ,
189+ } ) ,
190+ ( None , None , Some ( gssi) ) => Ok ( ResolvedInput {
191+ input : input. to_path_buf ( ) ,
192+ kind : FormatKind :: Gssi ,
193+ header : gssi. clone ( ) ,
194+ data : gssi. clone ( ) ,
195+ coordinates : resolve_sidecar ( & gssi, "dzg" ) ,
139196 } ) ,
140- ( true , true ) => Err ( format ! (
197+ ( Some ( ramac ) , Some ( pulseekko ) , None ) => Err ( format ! (
141198 "Ambiguous extension-less input {:?}: both {:?} and {:?} exist." ,
142199 input, ramac, pulseekko
143200 ) ) ,
144- ( false , false ) => Err ( format ! (
145- "Could not infer format for extension-less input {:?}. Tried {:?} and {:?}." ,
146- input, ramac, pulseekko
201+ ( Some ( ramac) , None , Some ( gssi) ) => Err ( format ! (
202+ "Ambiguous extension-less input {:?}: both {:?} and {:?} exist." ,
203+ input, ramac, gssi
204+ ) ) ,
205+ ( None , Some ( pulseekko) , Some ( gssi) ) => Err ( format ! (
206+ "Ambiguous extension-less input {:?}: both {:?} and {:?} exist." ,
207+ input, pulseekko, gssi
208+ ) ) ,
209+ ( Some ( ramac) , Some ( pulseekko) , Some ( gssi) ) => Err ( format ! (
210+ "Ambiguous extension-less input {:?}: {:?}, {:?}, and {:?} exist." ,
211+ input, ramac, pulseekko, gssi
212+ ) ) ,
213+ ( None , None , None ) => Err ( format ! (
214+ "Could not infer format for extension-less input {:?}. Tried {:?}, {:?}, and {:?}." ,
215+ input,
216+ input. with_extension( "rad" ) ,
217+ input. with_extension( "hd" ) ,
218+ input. with_extension( "dzt" )
147219 ) ) ,
148220 }
149221 }
@@ -160,7 +232,14 @@ mod tests {
160232 . into_iter ( )
161233 . map ( |fmt| fmt. name . to_string ( ) )
162234 . collect :: < Vec < String > > ( ) ;
163- assert_eq ! ( names, vec![ "ramac" . to_string( ) , "pulseekko" . to_string( ) ] ) ;
235+ assert_eq ! (
236+ names,
237+ vec![
238+ "ramac" . to_string( ) ,
239+ "pulseekko" . to_string( ) ,
240+ "gssi" . to_string( )
241+ ]
242+ ) ;
164243 }
165244
166245 #[ test]
@@ -186,4 +265,43 @@ mod tests {
186265 assert_eq ! ( resolved. kind, FormatKind :: PulseEkko ) ;
187266 assert_eq ! ( resolved. header, PathBuf :: from( "line01.hd" ) ) ;
188267 }
268+
269+ #[ test]
270+ fn test_resolve_gssi_extensions ( ) {
271+ let resolved = resolve_input ( Path :: new ( "line01.dzt" ) ) . unwrap ( ) ;
272+ assert_eq ! ( resolved. kind, FormatKind :: Gssi ) ;
273+ assert_eq ! ( resolved. data, PathBuf :: from( "line01.dzt" ) ) ;
274+ assert_eq ! ( resolved. coordinates, PathBuf :: from( "line01.dzg" ) ) ;
275+
276+ let resolved = resolve_input ( Path :: new ( "line01.dzg" ) ) . unwrap ( ) ;
277+ assert_eq ! ( resolved. kind, FormatKind :: Gssi ) ;
278+ assert_eq ! ( resolved. header, PathBuf :: from( "line01.dzt" ) ) ;
279+ }
280+
281+ #[ test]
282+ fn test_resolve_case_insensitive_sidecars ( ) {
283+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
284+ let rad = temp_dir. path ( ) . join ( "LINE01.RAD" ) ;
285+ let rd3 = temp_dir. path ( ) . join ( "LINE01.RD3" ) ;
286+ let cor = temp_dir. path ( ) . join ( "LINE01.COR" ) ;
287+ std:: fs:: write ( & rad, "" ) . unwrap ( ) ;
288+ std:: fs:: write ( & rd3, "" ) . unwrap ( ) ;
289+ std:: fs:: write ( & cor, "" ) . unwrap ( ) ;
290+
291+ let resolved = resolve_input ( & temp_dir. path ( ) . join ( "LINE01" ) ) . unwrap ( ) ;
292+ assert_eq ! ( resolved. kind, FormatKind :: Ramac ) ;
293+ assert_eq ! ( resolved. header, rad) ;
294+ assert_eq ! ( resolved. data, rd3) ;
295+ assert_eq ! ( resolved. coordinates, cor) ;
296+
297+ let dzt = temp_dir. path ( ) . join ( "TRACK.DZT" ) ;
298+ let dzg = temp_dir. path ( ) . join ( "TRACK.DZG" ) ;
299+ std:: fs:: write ( & dzt, "" ) . unwrap ( ) ;
300+ std:: fs:: write ( & dzg, "" ) . unwrap ( ) ;
301+
302+ let resolved = resolve_input ( & temp_dir. path ( ) . join ( "TRACK" ) ) . unwrap ( ) ;
303+ assert_eq ! ( resolved. kind, FormatKind :: Gssi ) ;
304+ assert_eq ! ( resolved. header, dzt) ;
305+ assert_eq ! ( resolved. coordinates, dzg) ;
306+ }
189307}
0 commit comments