fitsio/headers/
mod.rs

1//! Header-related code
2use 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
15/**
16Trait applied to types which can be read from a FITS header
17
18This is currently:
19
20* i32
21* i64
22* f32
23* f64
24* String
25* */
26pub 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
158/// Writing a fits keyword
159pub 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            // Scope ensures file is closed properly
381            {
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            // Scope ensures file is closed properly
406            {
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            // Scope ensures file is closed properly
436            {
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}