diff --git a/fitsio/examples/full_example.rs b/fitsio/examples/full_example.rs index 866c7375..185f8334 100644 --- a/fitsio/examples/full_example.rs +++ b/fitsio/examples/full_example.rs @@ -13,6 +13,7 @@ use std::error::Error; +use fitsio::headers::HeaderValue; use fitsio::images::{ImageDescription, ImageType}; use fitsio::tables::{ColumnDataType, ColumnDescription, FitsRow}; use fitsio::FitsFile; @@ -46,8 +47,12 @@ fn run() -> Result<(), Box> { /* First we get the primary HDU */ let hdu = fitsfile.primary_hdu()?; - /* Now we add the header keys */ - hdu.write_key(&mut fitsfile, "PROJECT", "My First Astronomy Project")?; + /* Now we add the header keys, including a comment */ + hdu.write_key( + &mut fitsfile, + "PROJECT", + ("My First Astronomy Project", "Project name"), + )?; /* Now the exposure time */ hdu.write_key(&mut fitsfile, "EXPTIME", 15.2f32)?; @@ -114,6 +119,27 @@ fn run() -> Result<(), Box> { /* Get the primary HDU and read a section of the image data */ let phdu = fitsfile.primary_hdu()?; + /* Read some information from the header. Start with the project */ + let project_value = phdu.read_key::>(&mut fitsfile, "PROJECT")?; + + /* `project_value` is a `HeaderValue` type, which has accessors for the value itself, as well + * as the comment */ + let project = project_value.value; + assert_eq!(project, "My First Astronomy Project"); + + let project_header_comment = project_value.comment; + assert_eq!(project_header_comment, Some("Project name".to_string())); + + /* Or of course destructuring is allowed */ + let HeaderValue { + value: _unused_value, + comment: _unused_comment, + } = phdu.read_key::>(&mut fitsfile, "PROJECT")?; + + /* Or primitive values can be read as well */ + let image_id: i64 = phdu.read_key(&mut fitsfile, "IMAGE_ID")?; + assert_eq!(image_id, 20180101010005i64); + /* Let's say we have a region around a star that we want to extract. The star is at (25, 25, * 1-indexed) and we want to extract a 5x5 box around it. This means we want to read rows 19 to * 19, and columns 19 to 29 (0-indexed). The range arguments are exclusive of the upper bound, diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs deleted file mode 100644 index a1c83911..00000000 --- a/fitsio/src/headers.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Header-related code -use crate::errors::{check_status, Result}; -use crate::fitsfile::FitsFile; -use crate::longnam::*; -use crate::types::DataType; -use std::ffi; -use std::ptr; - -const MAX_VALUE_LENGTH: usize = 71; - -/** -Trait applied to types which can be read from a FITS header - -This is currently: - -* i32 -* i64 -* f32 -* f64 -* String -* */ -pub trait ReadsKey { - #[doc(hidden)] - fn read_key(f: &mut FitsFile, name: &str) -> Result - where - Self: Sized; -} - -macro_rules! reads_key_impl { - ($t:ty, $func:ident) => { - impl ReadsKey for $t { - fn read_key(f: &mut FitsFile, name: &str) -> Result { - let c_name = ffi::CString::new(name)?; - let mut status = 0; - let mut value: Self = Self::default(); - - unsafe { - $func( - f.fptr.as_mut() as *mut _, - c_name.as_ptr(), - &mut value, - ptr::null_mut(), - &mut status, - ); - } - - check_status(status).map(|_| value) - } - } - }; -} - -reads_key_impl!(i32, fits_read_key_log); -#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] -reads_key_impl!(i64, fits_read_key_lng); -#[cfg(any(target_pointer_width = "32", target_os = "windows"))] -reads_key_impl!(i64, fits_read_key_lnglng); -reads_key_impl!(f32, fits_read_key_flt); -reads_key_impl!(f64, fits_read_key_dbl); - -impl ReadsKey for bool { - fn read_key(f: &mut FitsFile, name: &str) -> Result - where - Self: Sized, - { - let int_value = i32::read_key(f, name)?; - Ok(int_value > 0) - } -} - -impl ReadsKey for String { - fn read_key(f: &mut FitsFile, name: &str) -> Result { - let c_name = ffi::CString::new(name)?; - let mut status = 0; - let mut value: Vec = vec![0; MAX_VALUE_LENGTH]; - - unsafe { - fits_read_key_str( - f.fptr.as_mut() as *mut _, - c_name.as_ptr(), - value.as_mut_ptr(), - ptr::null_mut(), - &mut status, - ); - } - - check_status(status).and_then(|_| { - let value: Vec = value.iter().map(|&x| x as u8).filter(|&x| x != 0).collect(); - Ok(String::from_utf8(value)?) - }) - } -} - -/// Writing a fits keyword -pub trait WritesKey { - #[doc(hidden)] - fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()>; -} - -macro_rules! writes_key_impl_int { - ($t:ty, $datatype:expr) => { - impl WritesKey for $t { - fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { - let c_name = ffi::CString::new(name)?; - let mut status = 0; - - let datatype = u8::from($datatype); - - unsafe { - fits_write_key( - f.fptr.as_mut() as *mut _, - datatype as _, - c_name.as_ptr(), - &value as *const $t as *mut c_void, - ptr::null_mut(), - &mut status, - ); - } - check_status(status) - } - } - }; -} - -writes_key_impl_int!(i8, DataType::TSBYTE); -writes_key_impl_int!(i16, DataType::TSHORT); -writes_key_impl_int!(i32, DataType::TINT); -writes_key_impl_int!(i64, DataType::TLONG); -writes_key_impl_int!(u8, DataType::TBYTE); -writes_key_impl_int!(u16, DataType::TUSHORT); -writes_key_impl_int!(u32, DataType::TUINT); -writes_key_impl_int!(u64, DataType::TULONG); - -macro_rules! writes_key_impl_flt { - ($t:ty, $func:ident) => { - impl WritesKey for $t { - fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { - let c_name = ffi::CString::new(name)?; - let mut status = 0; - - unsafe { - $func( - f.fptr.as_mut() as *mut _, - c_name.as_ptr(), - value, - 9, - ptr::null_mut(), - &mut status, - ); - } - check_status(status) - } - } - }; -} - -writes_key_impl_flt!(f32, fits_write_key_flt); -writes_key_impl_flt!(f64, fits_write_key_dbl); - -impl WritesKey for String { - fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { - WritesKey::write_key(f, name, value.as_str()) - } -} - -impl<'a> WritesKey for &'a str { - fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { - let c_name = ffi::CString::new(name)?; - let c_value = ffi::CString::new(value)?; - let mut status = 0; - - unsafe { - fits_write_key_str( - f.fptr.as_mut() as *mut _, - c_name.as_ptr(), - c_value.as_ptr(), - ptr::null_mut(), - &mut status, - ); - } - - check_status(status) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testhelpers::{duplicate_test_file, floats_close_f64, with_temp_file}; - - #[test] - fn test_reading_header_keys() { - let mut f = FitsFile::open("../testdata/full_example.fits").unwrap(); - let hdu = f.hdu(0).unwrap(); - match hdu.read_key::(&mut f, "INTTEST") { - Ok(value) => assert_eq!(value, 42), - Err(e) => panic!("Error reading key: {:?}", e), - } - - match hdu.read_key::(&mut f, "DBLTEST") { - Ok(value) => assert!( - floats_close_f64(value, 0.09375), - "{:?} != {:?}", - value, - 0.09375 - ), - Err(e) => panic!("Error reading key: {:?}", e), - } - - match hdu.read_key::(&mut f, "TEST") { - Ok(value) => assert_eq!(value, "value"), - Err(e) => panic!("Error reading key: {:?}", e), - } - } - - #[test] - fn test_writing_header_keywords() { - with_temp_file(|filename| { - // Scope ensures file is closed properly - { - let mut f = FitsFile::create(filename).open().unwrap(); - f.hdu(0).unwrap().write_key(&mut f, "FOO", 1i64).unwrap(); - f.hdu(0) - .unwrap() - .write_key(&mut f, "BAR", "baz".to_string()) - .unwrap(); - } - - FitsFile::open(filename) - .map(|mut f| { - assert_eq!(f.hdu(0).unwrap().read_key::(&mut f, "foo").unwrap(), 1); - assert_eq!( - f.hdu(0).unwrap().read_key::(&mut f, "bar").unwrap(), - "baz".to_string() - ); - }) - .unwrap(); - }); - } - - #[test] - fn test_writing_integers() { - duplicate_test_file(|filename| { - let mut f = FitsFile::edit(filename).unwrap(); - let hdu = f.hdu(0).unwrap(); - hdu.write_key(&mut f, "ONE", 1i8).unwrap(); - hdu.write_key(&mut f, "TWO", 1i16).unwrap(); - hdu.write_key(&mut f, "THREE", 1i32).unwrap(); - hdu.write_key(&mut f, "FOUR", 1i64).unwrap(); - hdu.write_key(&mut f, "UONE", 1u8).unwrap(); - hdu.write_key(&mut f, "UTWO", 1u16).unwrap(); - hdu.write_key(&mut f, "UTHREE", 1u32).unwrap(); - hdu.write_key(&mut f, "UFOUR", 1u64).unwrap(); - }); - } - - #[test] - fn boolean_header_values() { - let mut f = FitsFile::open("../testdata/full_example.fits").unwrap(); - let hdu = f.primary_hdu().unwrap(); - - let res = dbg!(hdu.read_key::(&mut f, "SIMPLE").unwrap()); - - assert!(res); - } -} diff --git a/fitsio/src/headers/constants.rs b/fitsio/src/headers/constants.rs new file mode 100644 index 00000000..7a9a15b3 --- /dev/null +++ b/fitsio/src/headers/constants.rs @@ -0,0 +1,4 @@ +// FLEN_VALUE +pub(super) const MAX_VALUE_LENGTH: usize = 71; +// FLEN_COMMENT +pub(super) const MAX_COMMENT_LENGTH: usize = 73; diff --git a/fitsio/src/headers/header_value.rs b/fitsio/src/headers/header_value.rs new file mode 100644 index 00000000..3a3ea47a --- /dev/null +++ b/fitsio/src/headers/header_value.rs @@ -0,0 +1,123 @@ +//! Header values (values + comemnts) +//! + +use std::fmt::Debug; + +use super::ReadsKey; + +/// Struct representing a FITS header value +pub struct HeaderValue { + /// Value of the header card + pub value: T, + + /// Optional comment of the header card + pub comment: Option, +} + +/// Allow printing of `HeaderValue`s +impl Debug for HeaderValue +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HeaderValue") + .field("value", &self.value) + .field("comment", &self.comment) + .finish() + } +} + +/// Allow comparing of `HeaderValue`'s where the `value` is equatable +/// so that e.g. `HeaderValue` can be compared to `f64` +impl PartialEq for HeaderValue +where + T: PartialEq, +{ + fn eq(&self, other: &T) -> bool { + self.value == *other + } +} + +/// Allow `HeaderValue` to be cloned +/// +/// ```rust +/// # use fitsio::headers::HeaderValue; +/// let mut hv = HeaderValue { +/// value: 1u16, +/// comment: None, +/// }; +/// let hv2 = hv.clone(); +/// +/// hv.value = 10; +/// assert_eq!(hv2.value, 1); +/// ``` +impl Clone for HeaderValue +where + T: Clone, +{ + fn clone(&self) -> Self { + HeaderValue { + value: self.value.clone(), + comment: self.comment.clone(), + } + } +} + +/// Default value of `HeaderValue` +/// +/// ```rust +/// # use fitsio::headers::HeaderValue; +/// let hv = HeaderValue::::default(); +/// assert_eq!(hv.value, 0); +/// ``` +impl Default for HeaderValue +where + T: Default, +{ + fn default() -> Self { + Self { + value: Default::default(), + comment: Default::default(), + } + } +} + +impl HeaderValue +where + T: ReadsKey, +{ + /// Map the _value_ of a [`HeaderValue`] to another form + /// ```rust + /// # use fitsio::headers::HeaderValue; + /// let hv = HeaderValue { value: 1, comment: None }; + /// let hv2 = hv.map(|value| value * 2); + /// assert_eq!(hv2.value, 2); + /// ``` + pub fn map(self, f: F) -> HeaderValue + where + F: FnOnce(T) -> U, + { + HeaderValue { + value: f(self.value), + comment: self.comment, + } + } + + /// Monadic "bind" for [`HeaderValue`] + /// ```rust + /// # use fitsio::headers::HeaderValue; + /// let hv = HeaderValue { value: 1, comment: None }; + /// let hv2 = hv.and_then(|v| HeaderValue { + /// value: v * 2, + /// comment: Some("ok".to_string()), + /// }); + /// assert_eq!(hv2.value, 2); + /// assert_eq!(hv2.comment, Some("ok".to_string())); + /// ``` + pub fn and_then(self, f: F) -> HeaderValue + where + F: FnOnce(T) -> HeaderValue, + { + f(self.value) + } +} diff --git a/fitsio/src/headers/mod.rs b/fitsio/src/headers/mod.rs new file mode 100644 index 00000000..d24a77cf --- /dev/null +++ b/fitsio/src/headers/mod.rs @@ -0,0 +1,497 @@ +//! Header-related code +use crate::errors::{check_status, Result}; +use crate::fitsfile::FitsFile; +use crate::longnam::*; +use crate::types::DataType; +use std::ffi; +use std::ptr; + +mod constants; +mod header_value; + +use constants::{MAX_COMMENT_LENGTH, MAX_VALUE_LENGTH}; +pub use header_value::HeaderValue; + +/** +Trait applied to types which can be read from a FITS header + +This is currently: + +* i32 +* i64 +* f32 +* f64 +* String +* */ +pub trait ReadsKey { + #[doc(hidden)] + fn read_key(f: &mut FitsFile, name: &str) -> Result + where + Self: Sized; +} + +macro_rules! reads_key_impl { + ($t:ty, $func:ident) => { + impl ReadsKey for $t { + fn read_key(f: &mut FitsFile, name: &str) -> Result { + let hv: HeaderValue<$t> = ReadsKey::read_key(f, name)?; + Ok(hv.value) + } + } + impl ReadsKey for HeaderValue<$t> + where + $t: Default, + { + fn read_key(f: &mut FitsFile, name: &str) -> Result { + let c_name = ffi::CString::new(name)?; + let mut status = 0; + let mut value: Self = Default::default(); + let mut comment: Vec = vec![0; MAX_COMMENT_LENGTH]; + + unsafe { + $func( + f.fptr.as_mut() as *mut _, + c_name.as_ptr(), + &mut value.value, + comment.as_mut_ptr(), + &mut status, + ); + } + + check_status(status).map(|_| { + let comment = { + let comment: Vec = comment + .iter() + .map(|&x| x as u8) + .filter(|&x| x != 0) + .collect(); + if comment.is_empty() { + None + } else { + String::from_utf8(comment).ok() + } + }; + + value.comment = comment; + + value + }) + } + } + }; +} + +reads_key_impl!(i32, fits_read_key_log); +#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] +reads_key_impl!(i64, fits_read_key_lng); +#[cfg(any(target_pointer_width = "32", target_os = "windows"))] +reads_key_impl!(i64, fits_read_key_lnglng); +reads_key_impl!(f32, fits_read_key_flt); +reads_key_impl!(f64, fits_read_key_dbl); + +impl ReadsKey for bool { + fn read_key(f: &mut FitsFile, name: &str) -> Result + where + Self: Sized, + { + i32::read_key(f, name).map(|v| v > 0) + } +} + +impl ReadsKey for HeaderValue { + fn read_key(f: &mut FitsFile, name: &str) -> Result + where + Self: Sized, + { + let hv: HeaderValue = ReadsKey::read_key(f, name)?; + Ok(hv.map(|v| v > 0)) + } +} + +impl ReadsKey for String { + fn read_key(f: &mut FitsFile, name: &str) -> Result { + let hv: HeaderValue = ReadsKey::read_key(f, name)?; + Ok(hv.value) + } +} + +impl ReadsKey for HeaderValue { + fn read_key(f: &mut FitsFile, name: &str) -> Result { + let c_name = ffi::CString::new(name)?; + let mut status = 0; + let mut value: Vec = vec![0; MAX_VALUE_LENGTH]; + let mut comment: Vec = vec![0; MAX_COMMENT_LENGTH]; + + unsafe { + fits_read_key_str( + f.fptr.as_mut() as *mut _, + c_name.as_ptr(), + value.as_mut_ptr(), + comment.as_mut_ptr(), + &mut status, + ); + } + + check_status(status).and_then(|_| { + let value: Vec = value.iter().map(|&x| x as u8).filter(|&x| x != 0).collect(); + String::from_utf8(value) + .map(|value| { + let comment = { + let comment: Vec = comment + .iter() + .map(|&x| x as u8) + .filter(|&x| x != 0) + .collect(); + if comment.is_empty() { + None + } else { + String::from_utf8(comment).ok() + } + }; + HeaderValue { value, comment } + }) + .map_err(From::from) + }) + } +} + +/// Writing a fits keyword +pub trait WritesKey { + #[doc(hidden)] + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()>; +} + +macro_rules! writes_key_impl_int { + ($t:ty, $datatype:expr) => { + impl WritesKey for $t { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let c_name = ffi::CString::new(name)?; + let mut status = 0; + + let datatype = u8::from($datatype); + + unsafe { + fits_write_key( + f.fptr.as_mut() as *mut _, + datatype as _, + c_name.as_ptr(), + &value as *const $t as *mut c_void, + ptr::null_mut(), + &mut status, + ); + } + check_status(status) + } + } + + impl WritesKey for ($t, &str) { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let (value, comment) = value; + let c_name = ffi::CString::new(name)?; + let c_comment = ffi::CString::new(comment)?; + let mut status = 0; + + let datatype = u8::from($datatype); + + unsafe { + fits_write_key( + f.fptr.as_mut() as *mut _, + datatype as _, + c_name.as_ptr(), + &value as *const $t as *mut c_void, + c_comment.as_ptr(), + &mut status, + ); + } + check_status(status) + } + } + + impl WritesKey for ($t, String) { + #[inline(always)] + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let (value, comment) = value; + WritesKey::write_key(f, name, (value, comment.as_str())) + } + } + }; +} + +writes_key_impl_int!(i8, DataType::TSBYTE); +writes_key_impl_int!(i16, DataType::TSHORT); +writes_key_impl_int!(i32, DataType::TINT); +writes_key_impl_int!(i64, DataType::TLONG); +writes_key_impl_int!(u8, DataType::TBYTE); +writes_key_impl_int!(u16, DataType::TUSHORT); +writes_key_impl_int!(u32, DataType::TUINT); +writes_key_impl_int!(u64, DataType::TULONG); + +macro_rules! writes_key_impl_flt { + ($t:ty, $func:ident) => { + impl WritesKey for $t { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let c_name = ffi::CString::new(name)?; + let mut status = 0; + + unsafe { + $func( + f.fptr.as_mut() as *mut _, + c_name.as_ptr(), + value, + 9, + ptr::null_mut(), + &mut status, + ); + } + check_status(status) + } + } + + impl WritesKey for ($t, &str) { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let (value, comment) = value; + let c_name = ffi::CString::new(name)?; + let c_comment = ffi::CString::new(comment)?; + let mut status = 0; + + unsafe { + $func( + f.fptr.as_mut() as *mut _, + c_name.as_ptr(), + value, + 9, + c_comment.as_ptr(), + &mut status, + ); + } + check_status(status) + } + } + + impl WritesKey for ($t, String) { + #[inline(always)] + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let (value, comment) = value; + WritesKey::write_key(f, name, (value, comment.as_str())) + } + } + }; +} + +writes_key_impl_flt!(f32, fits_write_key_flt); +writes_key_impl_flt!(f64, fits_write_key_dbl); + +impl WritesKey for String { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + WritesKey::write_key(f, name, value.as_str()) + } +} + +impl<'a> WritesKey for &'a str { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let c_name = ffi::CString::new(name)?; + let c_value = ffi::CString::new(value)?; + let mut status = 0; + + unsafe { + fits_write_key_str( + f.fptr.as_mut() as *mut _, + c_name.as_ptr(), + c_value.as_ptr(), + ptr::null_mut(), + &mut status, + ); + } + + check_status(status) + } +} + +impl WritesKey for (String, &str) { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let (value, comment) = value; + WritesKey::write_key(f, name, (value.as_str(), comment)) + } +} + +impl WritesKey for (String, String) { + #[inline(always)] + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let (value, comment) = value; + WritesKey::write_key(f, name, (value.as_str(), comment.as_str())) + } +} + +impl<'a> WritesKey for (&'a str, &'a str) { + fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> { + let (value, comment) = value; + let c_name = ffi::CString::new(name)?; + let c_value = ffi::CString::new(value)?; + let c_comment = ffi::CString::new(comment)?; + let mut status = 0; + + unsafe { + fits_write_key_str( + f.fptr.as_mut() as *mut _, + c_name.as_ptr(), + c_value.as_ptr(), + c_comment.as_ptr(), + &mut status, + ); + } + + check_status(status) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::testhelpers::{duplicate_test_file, floats_close_f64, with_temp_file}; + + #[test] + fn test_reading_header_keys() { + let mut f = FitsFile::open("../testdata/full_example.fits").unwrap(); + let hdu = f.hdu(0).unwrap(); + match hdu.read_key::(&mut f, "INTTEST") { + Ok(value) => assert_eq!(value, 42), + Err(e) => panic!("Error reading key: {:?}", e), + } + + match hdu.read_key::(&mut f, "DBLTEST") { + Ok(value) => assert!( + floats_close_f64(value, 0.09375), + "{:?} != {:?}", + value, + 0.09375 + ), + Err(e) => panic!("Error reading key: {:?}", e), + } + + match hdu.read_key::(&mut f, "TEST") { + Ok(value) => assert_eq!(value, "value"), + Err(e) => panic!("Error reading key: {:?}", e), + } + } + + #[test] + fn test_writing_header_keywords() { + with_temp_file(|filename| { + // Scope ensures file is closed properly + { + let mut f = FitsFile::create(filename).open().unwrap(); + f.hdu(0).unwrap().write_key(&mut f, "FOO", 1i64).unwrap(); + f.hdu(0) + .unwrap() + .write_key(&mut f, "BAR", "baz".to_string()) + .unwrap(); + } + + FitsFile::open(filename) + .map(|mut f| { + assert_eq!(f.hdu(0).unwrap().read_key::(&mut f, "foo").unwrap(), 1); + assert_eq!( + f.hdu(0).unwrap().read_key::(&mut f, "bar").unwrap(), + "baz".to_string() + ); + }) + .unwrap(); + }); + } + + #[test] + fn test_writing_with_comments() { + with_temp_file(|filename| { + // Scope ensures file is closed properly + { + let mut f = FitsFile::create(filename).open().unwrap(); + f.hdu(0) + .unwrap() + .write_key(&mut f, "FOO", (1i64, "Foo value")) + .unwrap(); + f.hdu(0) + .unwrap() + .write_key(&mut f, "BAR", ("baz".to_string(), "baz value")) + .unwrap(); + } + + FitsFile::open(filename) + .map(|mut f| { + let foo_header_value = f + .hdu(0) + .unwrap() + .read_key::>(&mut f, "foo") + .unwrap(); + assert_eq!(foo_header_value.value, 1); + assert_eq!(foo_header_value.comment, Some("Foo value".to_string())); + }) + .unwrap(); + }); + } + + #[test] + fn test_writing_reading_empty_comment() { + with_temp_file(|filename| { + // Scope ensures file is closed properly + { + let mut f = FitsFile::create(filename).open().unwrap(); + f.hdu(0) + .unwrap() + .write_key(&mut f, "FOO", (1i64, "")) + .unwrap(); + } + + FitsFile::open(filename) + .map(|mut f| { + let foo_header_value = f + .hdu(0) + .unwrap() + .read_key::>(&mut f, "foo") + .unwrap(); + assert_eq!(foo_header_value.value, 1); + assert!(foo_header_value.comment.is_none()); + }) + .unwrap(); + }); + } + + #[test] + fn test_writing_integers() { + duplicate_test_file(|filename| { + let mut f = FitsFile::edit(filename).unwrap(); + let hdu = f.hdu(0).unwrap(); + hdu.write_key(&mut f, "ONE", 1i8).unwrap(); + hdu.write_key(&mut f, "TWO", 1i16).unwrap(); + hdu.write_key(&mut f, "THREE", 1i32).unwrap(); + hdu.write_key(&mut f, "FOUR", 1i64).unwrap(); + hdu.write_key(&mut f, "UONE", 1u8).unwrap(); + hdu.write_key(&mut f, "UTWO", 1u16).unwrap(); + hdu.write_key(&mut f, "UTHREE", 1u32).unwrap(); + hdu.write_key(&mut f, "UFOUR", 1u64).unwrap(); + }); + } + + #[test] + fn boolean_header_values() { + let mut f = FitsFile::open("../testdata/full_example.fits").unwrap(); + let hdu = f.primary_hdu().unwrap(); + + let res = hdu.read_key::(&mut f, "SIMPLE").unwrap(); + assert!(res); + } +} + +#[cfg(test)] +mod headervalue_tests { + use super::HeaderValue; + + #[test] + fn equate_different_types() { + let v = HeaderValue { + value: 1i64, + comment: Some("".to_string()), + }; + + assert_eq!(v, 1i64); + } +} diff --git a/fitsio/src/images.rs b/fitsio/src/images.rs index b57b0254..5466c45f 100644 --- a/fitsio/src/images.rs +++ b/fitsio/src/images.rs @@ -380,7 +380,6 @@ imagetype_into_impl!(i64); mod tests { use super::*; use crate::errors::Error; - use crate::fitsfile::FitsFile; use crate::testhelpers::with_temp_file; #[test] diff --git a/fitsio/src/lib.rs b/fitsio/src/lib.rs index 0e4969d6..fdc40af0 100644 --- a/fitsio/src/lib.rs +++ b/fitsio/src/lib.rs @@ -375,7 +375,7 @@ Header keys are read through the [`read_key`][fits-hdu-read-key] function, and is generic over types that implement the [`ReadsKey`][reads-key] trait: ```rust -# fn try_main() -> Result<(), Box> { +# fn try_main() -> Result<(), Box> { # let filename = "../testdata/full_example.fits"; # let mut fptr = fitsio::FitsFile::open(filename)?; # { @@ -387,18 +387,35 @@ let int_value: i64 = fptr.hdu(0)?.read_key(&mut fptr, "INTTEST")?; let int_value = fptr.hdu(0)?.read_key::(&mut fptr, "INTTEST")?; # } + // Or let the compiler infer the types (if possible) # Ok(()) # } # fn main() { try_main().unwrap(); } ``` -Header cards can be written through the method -[`write_key`][fits-hdu-write-key]. It takes a key name and value. See [the -`WritesKey`][writes-key] trait for supported data types. +[`HeaderValue`] also implements the [`ReadsKey`][reads-key] trait, and allows the reading of comments: ```rust -# fn try_main() -> Result<(), Box> { +# fn try_main() -> Result<(), Box> { +# use fitsio::HeaderValue; +# let filename = "../testdata/full_example.fits"; +# let mut fptr = fitsio::FitsFile::open(filename)?; +# { +let int_value_with_comment: HeaderValue = fptr.hdu(0)?.read_key(&mut fptr, "INTTEST")?; +let HeaderValue { value, comment } = int_value_with_comment; +# } +# Ok(()) +# } +``` + + +Header cards can be written through the method [`write_key`][fits-hdu-write-key]. +It takes a key name and value, or a key name and value-comment tuple. +See the [`WritesKey`][writes-key] trait for supported data types. + +```rust +# fn try_main() -> Result<(), Box> { # let tdir = tempfile::Builder::new().prefix("fitsio-").tempdir().unwrap(); # let tdir_path = tdir.path(); # let filename = tdir_path.join("test.fits"); @@ -406,6 +423,14 @@ Header cards can be written through the method # let mut fptr = fitsio::FitsFile::create(filename).open()?; fptr.hdu(0)?.write_key(&mut fptr, "foo", 1i64)?; assert_eq!(fptr.hdu(0)?.read_key::(&mut fptr, "foo")?, 1i64); + +// with comments +# use fitsio::HeaderValue; +fptr.hdu(0)?.write_key(&mut fptr, "bar", (1i64, "bar comment"))?; + +let HeaderValue { value, comment } = fptr.hdu(0)?.read_key::>(&mut fptr, "bar")?; +assert_eq!(value, 1i64); +assert_eq!(comment, Some("bar comment".to_string())); # Ok(()) # } # } @@ -1001,7 +1026,7 @@ let _hdu = t.hdu(hdu_num).unwrap(); [image-description]: images/struct.ImageDescription.html [reads-col]: tables/trait.ReadsCol.html [reads-key]: headers/trait.ReadsKey.html -[writes-key]: headers/trait.ReadsKey.html +[writes-key]: headers/trait.WritesKey.html [new-fits-file]: fitsfile/struct.NewFitsFile.html [new-fits-file-open]: fitsfile/struct.NewFitsFile.html#method.open [new-fits-file-with-custom-primary]: fitsfile/struct.NewFitsFile.html#method.with_custom_primary @@ -1046,6 +1071,7 @@ pub mod errors; // Re-exports pub use crate::fitsfile::{FileOpenMode, FitsFile}; +pub use crate::headers::HeaderValue; // For custom derive purposes // pub use tables::FitsRow; diff --git a/fitsio/src/ndarray_compat.rs b/fitsio/src/ndarray_compat.rs index 6964040d..46c7170b 100644 --- a/fitsio/src/ndarray_compat.rs +++ b/fitsio/src/ndarray_compat.rs @@ -385,7 +385,6 @@ mod tests { // [14, 15], // [16, 17]]], - // [[[18, 19], // [20, 21], // [22, 23]],