1use crate::errors::{check_status, Result};
3use crate::fitsfile::FitsFile;
4use crate::longnam::*;
5use crate::types::DataType;
6use std::ffi;
7use std::ptr;
8
9mod constants;
10mod header_value;
11
12use constants::{MAX_COMMENT_LENGTH, MAX_VALUE_LENGTH};
13pub use header_value::HeaderValue;
14
15pub trait ReadsKey {
27 #[doc(hidden)]
28 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
29 where
30 Self: Sized;
31}
32
33macro_rules! reads_key_impl {
34 ($t:ty, $func:ident) => {
35 impl ReadsKey for $t {
36 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
37 let hv: HeaderValue<$t> = ReadsKey::read_key(f, name)?;
38 Ok(hv.value)
39 }
40 }
41 impl ReadsKey for HeaderValue<$t>
42 where
43 $t: Default,
44 {
45 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
46 let c_name = ffi::CString::new(name)?;
47 let mut status = 0;
48 let mut value: Self = Default::default();
49 let mut comment: Vec<c_char> = vec![0; MAX_COMMENT_LENGTH];
50
51 unsafe {
52 $func(
53 f.fptr.as_mut() as *mut _,
54 c_name.as_ptr(),
55 &mut value.value,
56 comment.as_mut_ptr(),
57 &mut status,
58 );
59 }
60
61 check_status(status).map(|_| {
62 let comment = {
63 let comment: Vec<u8> = comment
64 .iter()
65 .map(|&x| x as u8)
66 .filter(|&x| x != 0)
67 .collect();
68 if comment.is_empty() {
69 None
70 } else {
71 String::from_utf8(comment).ok()
72 }
73 };
74
75 value.comment = comment;
76
77 value
78 })
79 }
80 }
81 };
82}
83
84reads_key_impl!(i32, fits_read_key_log);
85#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
86reads_key_impl!(i64, fits_read_key_lng);
87#[cfg(any(target_pointer_width = "32", target_os = "windows"))]
88reads_key_impl!(i64, fits_read_key_lnglng);
89reads_key_impl!(f32, fits_read_key_flt);
90reads_key_impl!(f64, fits_read_key_dbl);
91
92impl ReadsKey for bool {
93 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
94 where
95 Self: Sized,
96 {
97 i32::read_key(f, name).map(|v| v > 0)
98 }
99}
100
101impl ReadsKey for HeaderValue<bool> {
102 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
103 where
104 Self: Sized,
105 {
106 let hv: HeaderValue<i32> = ReadsKey::read_key(f, name)?;
107 Ok(hv.map(|v| v > 0))
108 }
109}
110
111impl ReadsKey for String {
112 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
113 let hv: HeaderValue<String> = ReadsKey::read_key(f, name)?;
114 Ok(hv.value)
115 }
116}
117
118impl ReadsKey for HeaderValue<String> {
119 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
120 let c_name = ffi::CString::new(name)?;
121 let mut status = 0;
122 let mut value: Vec<c_char> = vec![0; MAX_VALUE_LENGTH];
123 let mut comment: Vec<c_char> = vec![0; MAX_COMMENT_LENGTH];
124
125 unsafe {
126 fits_read_key_str(
127 f.fptr.as_mut() as *mut _,
128 c_name.as_ptr(),
129 value.as_mut_ptr(),
130 comment.as_mut_ptr(),
131 &mut status,
132 );
133 }
134
135 check_status(status).and_then(|_| {
136 let value: Vec<u8> = value.iter().map(|&x| x as u8).filter(|&x| x != 0).collect();
137 String::from_utf8(value)
138 .map(|value| {
139 let comment = {
140 let comment: Vec<u8> = comment
141 .iter()
142 .map(|&x| x as u8)
143 .filter(|&x| x != 0)
144 .collect();
145 if comment.is_empty() {
146 None
147 } else {
148 String::from_utf8(comment).ok()
149 }
150 };
151 HeaderValue { value, comment }
152 })
153 .map_err(From::from)
154 })
155 }
156}
157
158pub trait WritesKey {
160 #[doc(hidden)]
161 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()>;
162}
163
164macro_rules! writes_key_impl_int {
165 ($t:ty, $datatype:expr) => {
166 impl WritesKey for $t {
167 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
168 let c_name = ffi::CString::new(name)?;
169 let mut status = 0;
170
171 let datatype = u8::from($datatype);
172
173 unsafe {
174 fits_write_key(
175 f.fptr.as_mut() as *mut _,
176 datatype as _,
177 c_name.as_ptr(),
178 &value as *const $t as *mut c_void,
179 ptr::null_mut(),
180 &mut status,
181 );
182 }
183 check_status(status)
184 }
185 }
186
187 impl WritesKey for ($t, &str) {
188 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
189 let (value, comment) = value;
190 let c_name = ffi::CString::new(name)?;
191 let c_comment = ffi::CString::new(comment)?;
192 let mut status = 0;
193
194 let datatype = u8::from($datatype);
195
196 unsafe {
197 fits_write_key(
198 f.fptr.as_mut() as *mut _,
199 datatype as _,
200 c_name.as_ptr(),
201 &value as *const $t as *mut c_void,
202 c_comment.as_ptr(),
203 &mut status,
204 );
205 }
206 check_status(status)
207 }
208 }
209
210 impl WritesKey for ($t, String) {
211 #[inline(always)]
212 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
213 let (value, comment) = value;
214 WritesKey::write_key(f, name, (value, comment.as_str()))
215 }
216 }
217 };
218}
219
220writes_key_impl_int!(i8, DataType::TSBYTE);
221writes_key_impl_int!(i16, DataType::TSHORT);
222writes_key_impl_int!(i32, DataType::TINT);
223writes_key_impl_int!(i64, DataType::TLONG);
224writes_key_impl_int!(u8, DataType::TBYTE);
225writes_key_impl_int!(u16, DataType::TUSHORT);
226writes_key_impl_int!(u32, DataType::TUINT);
227writes_key_impl_int!(u64, DataType::TULONG);
228
229macro_rules! writes_key_impl_flt {
230 ($t:ty, $func:ident) => {
231 impl WritesKey for $t {
232 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
233 let c_name = ffi::CString::new(name)?;
234 let mut status = 0;
235
236 unsafe {
237 $func(
238 f.fptr.as_mut() as *mut _,
239 c_name.as_ptr(),
240 value,
241 9,
242 ptr::null_mut(),
243 &mut status,
244 );
245 }
246 check_status(status)
247 }
248 }
249
250 impl WritesKey for ($t, &str) {
251 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
252 let (value, comment) = value;
253 let c_name = ffi::CString::new(name)?;
254 let c_comment = ffi::CString::new(comment)?;
255 let mut status = 0;
256
257 unsafe {
258 $func(
259 f.fptr.as_mut() as *mut _,
260 c_name.as_ptr(),
261 value,
262 9,
263 c_comment.as_ptr(),
264 &mut status,
265 );
266 }
267 check_status(status)
268 }
269 }
270
271 impl WritesKey for ($t, String) {
272 #[inline(always)]
273 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
274 let (value, comment) = value;
275 WritesKey::write_key(f, name, (value, comment.as_str()))
276 }
277 }
278 };
279}
280
281writes_key_impl_flt!(f32, fits_write_key_flt);
282writes_key_impl_flt!(f64, fits_write_key_dbl);
283
284impl WritesKey for String {
285 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
286 WritesKey::write_key(f, name, value.as_str())
287 }
288}
289
290impl WritesKey for &'_ str {
291 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
292 let c_name = ffi::CString::new(name)?;
293 let c_value = ffi::CString::new(value)?;
294 let mut status = 0;
295
296 unsafe {
297 fits_write_key_str(
298 f.fptr.as_mut() as *mut _,
299 c_name.as_ptr(),
300 c_value.as_ptr(),
301 ptr::null_mut(),
302 &mut status,
303 );
304 }
305
306 check_status(status)
307 }
308}
309
310impl WritesKey for (String, &str) {
311 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
312 let (value, comment) = value;
313 WritesKey::write_key(f, name, (value.as_str(), comment))
314 }
315}
316
317impl WritesKey for (String, String) {
318 #[inline(always)]
319 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
320 let (value, comment) = value;
321 WritesKey::write_key(f, name, (value.as_str(), comment.as_str()))
322 }
323}
324
325impl<'a> WritesKey for (&'a str, &'a str) {
326 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
327 let (value, comment) = value;
328 let c_name = ffi::CString::new(name)?;
329 let c_value = ffi::CString::new(value)?;
330 let c_comment = ffi::CString::new(comment)?;
331 let mut status = 0;
332
333 unsafe {
334 fits_write_key_str(
335 f.fptr.as_mut() as *mut _,
336 c_name.as_ptr(),
337 c_value.as_ptr(),
338 c_comment.as_ptr(),
339 &mut status,
340 );
341 }
342
343 check_status(status)
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use crate::testhelpers::{duplicate_test_file, floats_close_f64, with_temp_file};
351
352 #[test]
353 fn test_reading_header_keys() {
354 let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
355 let hdu = f.hdu(0).unwrap();
356 match hdu.read_key::<i64>(&mut f, "INTTEST") {
357 Ok(value) => assert_eq!(value, 42),
358 Err(e) => panic!("Error reading key: {:?}", e),
359 }
360
361 match hdu.read_key::<f64>(&mut f, "DBLTEST") {
362 Ok(value) => assert!(
363 floats_close_f64(value, 0.09375),
364 "{:?} != {:?}",
365 value,
366 0.09375
367 ),
368 Err(e) => panic!("Error reading key: {:?}", e),
369 }
370
371 match hdu.read_key::<String>(&mut f, "TEST") {
372 Ok(value) => assert_eq!(value, "value"),
373 Err(e) => panic!("Error reading key: {:?}", e),
374 }
375 }
376
377 #[test]
378 fn test_writing_header_keywords() {
379 with_temp_file(|filename| {
380 {
382 let mut f = FitsFile::create(filename).open().unwrap();
383 f.hdu(0).unwrap().write_key(&mut f, "FOO", 1i64).unwrap();
384 f.hdu(0)
385 .unwrap()
386 .write_key(&mut f, "BAR", "baz".to_string())
387 .unwrap();
388 }
389
390 FitsFile::open(filename)
391 .map(|mut f| {
392 assert_eq!(f.hdu(0).unwrap().read_key::<i64>(&mut f, "foo").unwrap(), 1);
393 assert_eq!(
394 f.hdu(0).unwrap().read_key::<String>(&mut f, "bar").unwrap(),
395 "baz".to_string()
396 );
397 })
398 .unwrap();
399 });
400 }
401
402 #[test]
403 fn test_writing_with_comments() {
404 with_temp_file(|filename| {
405 {
407 let mut f = FitsFile::create(filename).open().unwrap();
408 f.hdu(0)
409 .unwrap()
410 .write_key(&mut f, "FOO", (1i64, "Foo value"))
411 .unwrap();
412 f.hdu(0)
413 .unwrap()
414 .write_key(&mut f, "BAR", ("baz".to_string(), "baz value"))
415 .unwrap();
416 }
417
418 FitsFile::open(filename)
419 .map(|mut f| {
420 let foo_header_value = f
421 .hdu(0)
422 .unwrap()
423 .read_key::<HeaderValue<i64>>(&mut f, "foo")
424 .unwrap();
425 assert_eq!(foo_header_value.value, 1);
426 assert_eq!(foo_header_value.comment, Some("Foo value".to_string()));
427 })
428 .unwrap();
429 });
430 }
431
432 #[test]
433 fn test_writing_reading_empty_comment() {
434 with_temp_file(|filename| {
435 {
437 let mut f = FitsFile::create(filename).open().unwrap();
438 f.hdu(0)
439 .unwrap()
440 .write_key(&mut f, "FOO", (1i64, ""))
441 .unwrap();
442 }
443
444 FitsFile::open(filename)
445 .map(|mut f| {
446 let foo_header_value = f
447 .hdu(0)
448 .unwrap()
449 .read_key::<HeaderValue<i64>>(&mut f, "foo")
450 .unwrap();
451 assert_eq!(foo_header_value.value, 1);
452 assert!(foo_header_value.comment.is_none());
453 })
454 .unwrap();
455 });
456 }
457
458 #[test]
459 fn test_writing_integers() {
460 duplicate_test_file(|filename| {
461 let mut f = FitsFile::edit(filename).unwrap();
462 let hdu = f.hdu(0).unwrap();
463 hdu.write_key(&mut f, "ONE", 1i8).unwrap();
464 hdu.write_key(&mut f, "TWO", 1i16).unwrap();
465 hdu.write_key(&mut f, "THREE", 1i32).unwrap();
466 hdu.write_key(&mut f, "FOUR", 1i64).unwrap();
467 hdu.write_key(&mut f, "UONE", 1u8).unwrap();
468 hdu.write_key(&mut f, "UTWO", 1u16).unwrap();
469 hdu.write_key(&mut f, "UTHREE", 1u32).unwrap();
470 hdu.write_key(&mut f, "UFOUR", 1u64).unwrap();
471 });
472 }
473
474 #[test]
475 fn boolean_header_values() {
476 let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
477 let hdu = f.primary_hdu().unwrap();
478
479 let res = hdu.read_key::<bool>(&mut f, "SIMPLE").unwrap();
480 assert!(res);
481 }
482}
483
484#[cfg(test)]
485mod headervalue_tests {
486 use super::HeaderValue;
487
488 #[test]
489 fn equate_different_types() {
490 let v = HeaderValue {
491 value: 1i64,
492 comment: Some("".to_string()),
493 };
494
495 assert_eq!(v, 1i64);
496 }
497}