From c2c5ffc7f7b063207f0ae285f8d5026882ffa505 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 22:03:50 +0100 Subject: [PATCH 01/18] Remove extra import --- fitsio/src/images.rs | 1 - 1 file changed, 1 deletion(-) 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] From 357581eaa03eb7eb62d96b4c24a4aaf423f92d07 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 22:14:11 +0100 Subject: [PATCH 02/18] Implement writing keys with comments --- fitsio/src/headers.rs | 125 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index a1c83911..14b37cdc 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -119,6 +119,37 @@ macro_rules! writes_key_impl_int { 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())) + } + } }; } @@ -151,6 +182,35 @@ macro_rules! writes_key_impl_flt { 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())) + } + } }; } @@ -183,6 +243,43 @@ impl<'a> WritesKey for &'a str { } } +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::*; @@ -238,6 +335,34 @@ mod tests { }); } + #[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| { + 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| { From 5396a63f58c150dc1ce96e12ce6b446fa0fbcc86 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 23:03:23 +0100 Subject: [PATCH 03/18] Implement read_key with comments --- fitsio/examples/full_example.rs | 8 +- fitsio/src/fitsfile.rs | 8 +- fitsio/src/hdu.rs | 11 ++- fitsio/src/headers.rs | 134 ++++++++++++++++++++++++++++---- fitsio/src/lib.rs | 4 +- 5 files changed, 138 insertions(+), 27 deletions(-) diff --git a/fitsio/examples/full_example.rs b/fitsio/examples/full_example.rs index 866c7375..41f1e86a 100644 --- a/fitsio/examples/full_example.rs +++ b/fitsio/examples/full_example.rs @@ -46,8 +46,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)?; diff --git a/fitsio/src/fitsfile.rs b/fitsio/src/fitsfile.rs index e2ad1727..fed410a9 100644 --- a/fitsio/src/fitsfile.rs +++ b/fitsio/src/fitsfile.rs @@ -1076,7 +1076,7 @@ mod test { // Ensure the empty primary has been written let hdu = f.hdu(0).unwrap(); - let naxis: i64 = hdu.read_key(&mut f, "NAXIS").unwrap(); + let naxis: i64 = hdu.read_key(&mut f, "NAXIS").unwrap().value; assert_eq!(naxis, 0); }) .unwrap(); @@ -1396,7 +1396,7 @@ mod test { let hdu = f.current_hdu().unwrap(); assert_eq!( - hdu.read_key::(&mut f, "EXTNAME").unwrap(), + hdu.read_key::(&mut f, "EXTNAME").unwrap().value, "TESTEXT".to_string() ); } @@ -1438,7 +1438,7 @@ mod test { .create_image("foo".to_string(), &image_description) .unwrap(); assert_eq!( - hdu.read_key::(&mut f, "EXTNAME").unwrap(), + hdu.read_key::(&mut f, "EXTNAME").unwrap().value, "foo".to_string() ); }); @@ -1456,7 +1456,7 @@ mod test { .create_table("foo".to_string(), &table_description) .unwrap(); assert_eq!( - hdu.read_key::(&mut f, "EXTNAME").unwrap(), + hdu.read_key::(&mut f, "EXTNAME").unwrap().value, "foo".to_string() ); }); diff --git a/fitsio/src/hdu.rs b/fitsio/src/hdu.rs index 5c6b1e62..97ee5882 100644 --- a/fitsio/src/hdu.rs +++ b/fitsio/src/hdu.rs @@ -3,7 +3,7 @@ use crate::errors::{check_status, Result}; use crate::fitsfile::CaseSensitivity; use crate::fitsfile::FitsFile; -use crate::headers::{ReadsKey, WritesKey}; +use crate::headers::{HeaderValue, ReadsKey, WritesKey}; use crate::images::{ImageType, ReadImage, WriteImage}; use crate::longnam::*; use crate::tables::{ @@ -41,6 +41,7 @@ impl FitsHdu { pub fn name(&self, fits_file: &mut FitsFile) -> Result { let extname = self .read_key(fits_file, "EXTNAME") + .map(|hv| hv.value) .unwrap_or_else(|_| "".to_string()); Ok(extname) } @@ -56,12 +57,16 @@ impl FitsHdu { # let mut fptr = fitsio::FitsFile::open(filename)?; # let hdu = fptr.primary_hdu()?; # { - let int_value: i64 = hdu.read_key(&mut fptr, "INTTEST")?; + let int_value: i64 = hdu.read_key(&mut fptr, "INTTEST")?.value; # } # Ok(()) # } */ - pub fn read_key(&self, fits_file: &mut FitsFile, name: &str) -> Result { + pub fn read_key( + &self, + fits_file: &mut FitsFile, + name: &str, + ) -> Result> { fits_file.make_current(self)?; T::read_key(fits_file, name) } diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index 14b37cdc..ed2042d7 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -4,10 +4,70 @@ use crate::fitsfile::FitsFile; use crate::longnam::*; use crate::types::DataType; use std::ffi; +use std::fmt::Debug; use std::ptr; const MAX_VALUE_LENGTH: usize = 71; +/// 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 std::fmt::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 + } +} + +impl HeaderValue +where + T: ReadsKey, +{ + /// Read the value from a header value + pub fn value(&self) -> &T { + &self.value + } + + /// Read the comment for a header value, if present + pub fn comment(&self) -> Option<&String> { + self.comment.as_ref() + } + + /// Map the _value_ of a `HeaderValue` to another form + pub fn map(self, f: F) -> HeaderValue + where + F: FnOnce(T) -> U, + { + HeaderValue { + value: f(self.value), + comment: self.comment, + } + } +} + /** Trait applied to types which can be read from a FITS header @@ -21,7 +81,7 @@ This is currently: * */ pub trait ReadsKey { #[doc(hidden)] - fn read_key(f: &mut FitsFile, name: &str) -> Result + fn read_key(f: &mut FitsFile, name: &str) -> Result> where Self: Sized; } @@ -29,7 +89,7 @@ pub trait ReadsKey { macro_rules! reads_key_impl { ($t:ty, $func:ident) => { impl ReadsKey for $t { - fn read_key(f: &mut FitsFile, name: &str) -> Result { + 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(); @@ -44,7 +104,10 @@ macro_rules! reads_key_impl { ); } - check_status(status).map(|_| value) + check_status(status).map(|_| HeaderValue { + value, + comment: None, + }) } } }; @@ -59,17 +122,16 @@ 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 + fn read_key(f: &mut FitsFile, name: &str) -> Result> where Self: Sized, { - let int_value = i32::read_key(f, name)?; - Ok(int_value > 0) + Ok(i32::read_key(f, name)?.map(|v| v > 0)) } } impl ReadsKey for String { - fn read_key(f: &mut FitsFile, name: &str) -> Result { + 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]; @@ -86,7 +148,10 @@ impl ReadsKey for String { 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)?) + Ok(HeaderValue { + value: String::from_utf8(value)?, + comment: None, + }) }) } } @@ -290,12 +355,12 @@ mod tests { 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), + Ok(HeaderValue { value, .. }) => assert_eq!(value, 42), Err(e) => panic!("Error reading key: {:?}", e), } match hdu.read_key::(&mut f, "DBLTEST") { - Ok(value) => assert!( + Ok(HeaderValue { value, .. }) => assert!( floats_close_f64(value, 0.09375), "{:?} != {:?}", value, @@ -305,7 +370,7 @@ mod tests { } match hdu.read_key::(&mut f, "TEST") { - Ok(value) => assert_eq!(value, "value"), + Ok(HeaderValue { value, .. }) => assert_eq!(value, "value"), Err(e) => panic!("Error reading key: {:?}", e), } } @@ -325,9 +390,20 @@ mod tests { 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(), + f.hdu(0) + .unwrap() + .read_key::(&mut f, "foo") + .unwrap() + .value, + 1 + ); + assert_eq!( + f.hdu(0) + .unwrap() + .read_key::(&mut f, "bar") + .unwrap() + .value, "baz".to_string() ); }) @@ -353,9 +429,20 @@ mod tests { 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(), + f.hdu(0) + .unwrap() + .read_key::(&mut f, "foo") + .unwrap() + .value, + 1 + ); + assert_eq!( + f.hdu(0) + .unwrap() + .read_key::(&mut f, "bar") + .unwrap() + .value, "baz".to_string() ); }) @@ -384,8 +471,23 @@ mod tests { 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()); + let res = dbg!(hdu.read_key::(&mut f, "SIMPLE").unwrap().value); 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/lib.rs b/fitsio/src/lib.rs index 0e4969d6..80e4eb60 100644 --- a/fitsio/src/lib.rs +++ b/fitsio/src/lib.rs @@ -379,7 +379,7 @@ and is generic over types that implement the [`ReadsKey`][reads-key] trait: # let filename = "../testdata/full_example.fits"; # let mut fptr = fitsio::FitsFile::open(filename)?; # { -let int_value: i64 = fptr.hdu(0)?.read_key(&mut fptr, "INTTEST")?; +let int_value: i64 = fptr.hdu(0)?.read_key(&mut fptr, "INTTEST")?.value; # } // Alternatively @@ -405,7 +405,7 @@ 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); +assert_eq!(fptr.hdu(0)?.read_key::(&mut fptr, "foo")?.value, 1i64); # Ok(()) # } # } From 8fb1957e0c769c42db2da74eac940c296a2e8868 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 23:03:48 +0100 Subject: [PATCH 04/18] Box --- fitsio/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fitsio/src/lib.rs b/fitsio/src/lib.rs index 80e4eb60..efba8938 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)?; # { From 95a46ca551ed41745e3764006e12980997252a9b Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 23:16:31 +0100 Subject: [PATCH 05/18] Read comments --- fitsio/src/headers.rs | 44 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index ed2042d7..0a610f55 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -7,7 +7,10 @@ use std::ffi; use std::fmt::Debug; use std::ptr; +// FLEN_VALUE const MAX_VALUE_LENGTH: usize = 71; +// FLEN_COMMENT +const MAX_COMMENT_LENGTH: usize = 73; /// Struct representing a FITS header value pub struct HeaderValue { @@ -93,20 +96,27 @@ macro_rules! reads_key_impl { let c_name = ffi::CString::new(name)?; let mut status = 0; let mut value: Self = Self::default(); + let mut comment: Vec = vec![0; MAX_COMMENT_LENGTH]; unsafe { $func( f.fptr.as_mut() as *mut _, c_name.as_ptr(), &mut value, - ptr::null_mut(), + comment.as_mut_ptr(), &mut status, ); } + let comment: Vec = comment + .iter() + .map(|&x| x as u8) + .filter(|&x| x != 0) + .collect(); + check_status(status).map(|_| HeaderValue { value, - comment: None, + comment: String::from_utf8(comment).ok(), }) } } @@ -135,22 +145,28 @@ impl ReadsKey for String { 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(), - ptr::null_mut(), + 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(); + let comment: Vec = comment + .iter() + .map(|&x| x as u8) + .filter(|&x| x != 0) + .collect(); Ok(HeaderValue { value: String::from_utf8(value)?, - comment: None, + comment: String::from_utf8(comment).ok(), }) }) } @@ -429,22 +445,10 @@ mod tests { FitsFile::open(filename) .map(|mut f| { - assert_eq!( - f.hdu(0) - .unwrap() - .read_key::(&mut f, "foo") - .unwrap() - .value, - 1 - ); - assert_eq!( - f.hdu(0) - .unwrap() - .read_key::(&mut f, "bar") - .unwrap() - .value, - "baz".to_string() - ); + 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(); }); From 9c650adf4522fd58bb816390011b2560973db8b6 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 23:24:46 +0100 Subject: [PATCH 06/18] Include `and_then` for `HeaderValue` --- fitsio/src/headers.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index 0a610f55..511be539 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -59,7 +59,7 @@ where self.comment.as_ref() } - /// Map the _value_ of a `HeaderValue` to another form + /// Map the _value_ of a [`HeaderValue`] to another form pub fn map(self, f: F) -> HeaderValue where F: FnOnce(T) -> U, @@ -69,6 +69,20 @@ where comment: self.comment, } } + + /// Monadic "bind" for [`HeaderValue`] + pub fn and_then(self, f: F) -> Result> + where + F: FnOnce(T) -> Result, + { + match f(self.value) { + Ok(value) => Ok(HeaderValue { + value, + comment: self.comment, + }), + Err(e) => Err(e), + } + } } /** From 433e2e5b2f9ea4a904efc051cf94c10c5c28557b Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 23:31:45 +0100 Subject: [PATCH 07/18] Update example to read header with comment --- fitsio/examples/full_example.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fitsio/examples/full_example.rs b/fitsio/examples/full_example.rs index 41f1e86a..45b8a011 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; @@ -118,6 +119,23 @@ 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")?; + /* 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, From e4e18fe2c177a7f19d58cff4039b52b3f558f333 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Sat, 20 Jul 2024 23:38:56 +0100 Subject: [PATCH 08/18] Allow comments to be `None` --- fitsio/src/headers.rs | 66 ++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index 511be539..b59357bb 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -122,16 +122,20 @@ macro_rules! reads_key_impl { ); } - let comment: Vec = comment - .iter() - .map(|&x| x as u8) - .filter(|&x| x != 0) - .collect(); - - check_status(status).map(|_| HeaderValue { - value, - comment: String::from_utf8(comment).ok(), - }) + 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() + } + }; + + check_status(status).map(|_| HeaderValue { value, comment }) } } }; @@ -173,14 +177,21 @@ impl ReadsKey for String { check_status(status).and_then(|_| { let value: Vec = value.iter().map(|&x| x as u8).filter(|&x| x != 0).collect(); - let comment: Vec = comment - .iter() - .map(|&x| x as u8) - .filter(|&x| x != 0) - .collect(); + 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() + } + }; Ok(HeaderValue { value: String::from_utf8(value)?, - comment: String::from_utf8(comment).ok(), + comment, }) }) } @@ -468,6 +479,29 @@ mod tests { }); } + #[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| { From 4a25a65a139e962c3d44f62572b9bb48d840c0f5 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Mon, 22 Jul 2024 09:20:29 +0100 Subject: [PATCH 09/18] Remove getter methods --- fitsio/src/headers.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index b59357bb..4ae159e5 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -49,16 +49,6 @@ impl HeaderValue where T: ReadsKey, { - /// Read the value from a header value - pub fn value(&self) -> &T { - &self.value - } - - /// Read the comment for a header value, if present - pub fn comment(&self) -> Option<&String> { - self.comment.as_ref() - } - /// Map the _value_ of a [`HeaderValue`] to another form pub fn map(self, f: F) -> HeaderValue where From 7d67dd5d01a82e4d984cbd223882c22328635f23 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Mon, 22 Jul 2024 09:20:58 +0100 Subject: [PATCH 10/18] Allow HeaderValue to be cloned --- fitsio/src/headers.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index 4ae159e5..ed04f643 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -45,6 +45,19 @@ where } } +/// Allow `HeaderValue` to be clnned +impl Clone for HeaderValue +where + T: Clone, +{ + fn clone(&self) -> Self { + HeaderValue { + value: self.value.clone(), + comment: self.comment.clone(), + } + } +} + impl HeaderValue where T: ReadsKey, From fb6d46f82aa013d8ea9b78575e93b70a39532177 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Wed, 24 Jul 2024 20:57:04 +0100 Subject: [PATCH 11/18] Support HeaderValue or primitive value --- fitsio/examples/full_example.rs | 4 +- fitsio/src/fitsfile.rs | 8 +- fitsio/src/hdu.rs | 11 +- fitsio/src/headers.rs | 185 ++++++++++++++++++++++---------- fitsio/src/lib.rs | 6 +- 5 files changed, 138 insertions(+), 76 deletions(-) diff --git a/fitsio/examples/full_example.rs b/fitsio/examples/full_example.rs index 45b8a011..73d93407 100644 --- a/fitsio/examples/full_example.rs +++ b/fitsio/examples/full_example.rs @@ -120,7 +120,7 @@ fn run() -> Result<(), Box> { let phdu = fitsfile.primary_hdu()?; /* Read some information from the header. Start with the project */ - let project_value = phdu.read_key::(&mut fitsfile, "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 */ @@ -134,7 +134,7 @@ fn run() -> Result<(), Box> { let HeaderValue { value: _unused_value, comment: _unused_comment, - } = phdu.read_key::(&mut fitsfile, "PROJECT")?; + } = phdu.read_key::>(&mut fitsfile, "PROJECT")?; /* 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 diff --git a/fitsio/src/fitsfile.rs b/fitsio/src/fitsfile.rs index fed410a9..e2ad1727 100644 --- a/fitsio/src/fitsfile.rs +++ b/fitsio/src/fitsfile.rs @@ -1076,7 +1076,7 @@ mod test { // Ensure the empty primary has been written let hdu = f.hdu(0).unwrap(); - let naxis: i64 = hdu.read_key(&mut f, "NAXIS").unwrap().value; + let naxis: i64 = hdu.read_key(&mut f, "NAXIS").unwrap(); assert_eq!(naxis, 0); }) .unwrap(); @@ -1396,7 +1396,7 @@ mod test { let hdu = f.current_hdu().unwrap(); assert_eq!( - hdu.read_key::(&mut f, "EXTNAME").unwrap().value, + hdu.read_key::(&mut f, "EXTNAME").unwrap(), "TESTEXT".to_string() ); } @@ -1438,7 +1438,7 @@ mod test { .create_image("foo".to_string(), &image_description) .unwrap(); assert_eq!( - hdu.read_key::(&mut f, "EXTNAME").unwrap().value, + hdu.read_key::(&mut f, "EXTNAME").unwrap(), "foo".to_string() ); }); @@ -1456,7 +1456,7 @@ mod test { .create_table("foo".to_string(), &table_description) .unwrap(); assert_eq!( - hdu.read_key::(&mut f, "EXTNAME").unwrap().value, + hdu.read_key::(&mut f, "EXTNAME").unwrap(), "foo".to_string() ); }); diff --git a/fitsio/src/hdu.rs b/fitsio/src/hdu.rs index 97ee5882..5c6b1e62 100644 --- a/fitsio/src/hdu.rs +++ b/fitsio/src/hdu.rs @@ -3,7 +3,7 @@ use crate::errors::{check_status, Result}; use crate::fitsfile::CaseSensitivity; use crate::fitsfile::FitsFile; -use crate::headers::{HeaderValue, ReadsKey, WritesKey}; +use crate::headers::{ReadsKey, WritesKey}; use crate::images::{ImageType, ReadImage, WriteImage}; use crate::longnam::*; use crate::tables::{ @@ -41,7 +41,6 @@ impl FitsHdu { pub fn name(&self, fits_file: &mut FitsFile) -> Result { let extname = self .read_key(fits_file, "EXTNAME") - .map(|hv| hv.value) .unwrap_or_else(|_| "".to_string()); Ok(extname) } @@ -57,16 +56,12 @@ impl FitsHdu { # let mut fptr = fitsio::FitsFile::open(filename)?; # let hdu = fptr.primary_hdu()?; # { - let int_value: i64 = hdu.read_key(&mut fptr, "INTTEST")?.value; + let int_value: i64 = hdu.read_key(&mut fptr, "INTTEST")?; # } # Ok(()) # } */ - pub fn read_key( - &self, - fits_file: &mut FitsFile, - name: &str, - ) -> Result> { + pub fn read_key(&self, fits_file: &mut FitsFile, name: &str) -> Result { fits_file.make_current(self)?; T::read_key(fits_file, name) } diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index ed04f643..714fa6e1 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -58,6 +58,18 @@ where } } +impl Default for HeaderValue +where + T: Default, +{ + fn default() -> Self { + Self { + value: Default::default(), + comment: Default::default(), + } + } +} + impl HeaderValue where T: ReadsKey, @@ -101,7 +113,7 @@ This is currently: * */ pub trait ReadsKey { #[doc(hidden)] - fn read_key(f: &mut FitsFile, name: &str) -> Result> + fn read_key(f: &mut FitsFile, name: &str) -> Result where Self: Sized; } @@ -109,36 +121,63 @@ pub trait ReadsKey { macro_rules! reads_key_impl { ($t:ty, $func:ident) => { impl ReadsKey for $t { - fn read_key(f: &mut FitsFile, name: &str) -> Result> { + fn read_key(f: &mut FitsFile, name: &str) -> Result { + // TODO: use HeaderValue here let c_name = ffi::CString::new(name)?; let mut status = 0; - let mut value: Self = Self::default(); - let mut comment: Vec = vec![0; MAX_COMMENT_LENGTH]; + let mut value: Self = Default::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) + } + } + 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, ); } - 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() - } - }; - - check_status(status).map(|_| HeaderValue { value, comment }) + 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 + }) } } }; @@ -153,16 +192,49 @@ 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> + 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, { - Ok(i32::read_key(f, name)?.map(|v| v > 0)) + 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> { + 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(); + String::from_utf8(value).map_err(From::from) + }) + } +} + +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]; @@ -180,22 +252,23 @@ impl ReadsKey for String { check_status(status).and_then(|_| { let value: Vec = value.iter().map(|&x| x as u8).filter(|&x| x != 0).collect(); - 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() - } - }; - Ok(HeaderValue { - value: String::from_utf8(value)?, - comment, - }) + 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) }) } } @@ -399,12 +472,12 @@ mod tests { let mut f = FitsFile::open("../testdata/full_example.fits").unwrap(); let hdu = f.hdu(0).unwrap(); match hdu.read_key::(&mut f, "INTTEST") { - Ok(HeaderValue { value, .. }) => assert_eq!(value, 42), + Ok(value) => assert_eq!(value, 42), Err(e) => panic!("Error reading key: {:?}", e), } match hdu.read_key::(&mut f, "DBLTEST") { - Ok(HeaderValue { value, .. }) => assert!( + Ok(value) => assert!( floats_close_f64(value, 0.09375), "{:?} != {:?}", value, @@ -414,7 +487,7 @@ mod tests { } match hdu.read_key::(&mut f, "TEST") { - Ok(HeaderValue { value, .. }) => assert_eq!(value, "value"), + Ok(value) => assert_eq!(value, "value"), Err(e) => panic!("Error reading key: {:?}", e), } } @@ -434,20 +507,9 @@ mod tests { 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, "foo") - .unwrap() - .value, - 1 - ); - assert_eq!( - f.hdu(0) - .unwrap() - .read_key::(&mut f, "bar") - .unwrap() - .value, + f.hdu(0).unwrap().read_key::(&mut f, "bar").unwrap(), "baz".to_string() ); }) @@ -473,8 +535,11 @@ mod tests { FitsFile::open(filename) .map(|mut f| { - let foo_header_value = - f.hdu(0).unwrap().read_key::(&mut f, "foo").unwrap(); + 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())); }) @@ -496,8 +561,11 @@ mod tests { FitsFile::open(filename) .map(|mut f| { - let foo_header_value = - f.hdu(0).unwrap().read_key::(&mut f, "foo").unwrap(); + 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()); }) @@ -526,8 +594,7 @@ mod tests { 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().value); - + let res = hdu.read_key::(&mut f, "SIMPLE").unwrap(); assert!(res); } } diff --git a/fitsio/src/lib.rs b/fitsio/src/lib.rs index efba8938..357293ea 100644 --- a/fitsio/src/lib.rs +++ b/fitsio/src/lib.rs @@ -379,7 +379,7 @@ and is generic over types that implement the [`ReadsKey`][reads-key] trait: # let filename = "../testdata/full_example.fits"; # let mut fptr = fitsio::FitsFile::open(filename)?; # { -let int_value: i64 = fptr.hdu(0)?.read_key(&mut fptr, "INTTEST")?.value; +let int_value: i64 = fptr.hdu(0)?.read_key(&mut fptr, "INTTEST")?; # } // Alternatively @@ -398,14 +398,14 @@ Header cards can be written through the method `WritesKey`][writes-key] trait for supported data types. ```rust -# fn try_main() -> Result<(), Box> { +# 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"); # { # 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")?.value, 1i64); +assert_eq!(fptr.hdu(0)?.read_key::(&mut fptr, "foo")?, 1i64); # Ok(()) # } # } From d0406a5d5b81e422fbcda00a8d37fe97fac13491 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Wed, 24 Jul 2024 20:59:52 +0100 Subject: [PATCH 12/18] Show example of primitive value header reading --- fitsio/examples/full_example.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fitsio/examples/full_example.rs b/fitsio/examples/full_example.rs index 73d93407..185f8334 100644 --- a/fitsio/examples/full_example.rs +++ b/fitsio/examples/full_example.rs @@ -136,6 +136,10 @@ fn run() -> Result<(), Box> { 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, From f3e17679149288e9aca70e3dc6b0cf079e686a15 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Wed, 24 Jul 2024 21:02:44 +0100 Subject: [PATCH 13/18] Use HeaderValue implementation in primitive implementation --- fitsio/src/headers.rs | 38 ++++---------------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/fitsio/src/headers.rs b/fitsio/src/headers.rs index 714fa6e1..2abc5302 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers.rs @@ -122,22 +122,8 @@ macro_rules! reads_key_impl { ($t:ty, $func:ident) => { impl ReadsKey for $t { fn read_key(f: &mut FitsFile, name: &str) -> Result { - // TODO: use HeaderValue here - let c_name = ffi::CString::new(name)?; - let mut status = 0; - let mut value: Self = Default::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) + let hv: HeaderValue<$t> = ReadsKey::read_key(f, name)?; + Ok(hv.value) } } impl ReadsKey for HeaderValue<$t> @@ -212,24 +198,8 @@ impl ReadsKey for HeaderValue { 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(); - String::from_utf8(value).map_err(From::from) - }) + let hv: HeaderValue = ReadsKey::read_key(f, name)?; + Ok(hv.value) } } From c6b60f6be6c6384a4f580a770bc8db55a14e51e2 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Wed, 24 Jul 2024 21:14:14 +0100 Subject: [PATCH 14/18] Restructure headers module --- fitsio/src/headers/constants.rs | 4 + fitsio/src/headers/header_value.rs | 95 ++++++++++++++++++++++ fitsio/src/{headers.rs => headers/mod.rs} | 96 +---------------------- fitsio/src/lib.rs | 1 + 4 files changed, 104 insertions(+), 92 deletions(-) create mode 100644 fitsio/src/headers/constants.rs create mode 100644 fitsio/src/headers/header_value.rs rename fitsio/src/{headers.rs => headers/mod.rs} (88%) 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..a4835dbd --- /dev/null +++ b/fitsio/src/headers/header_value.rs @@ -0,0 +1,95 @@ +//! Header values (values + comemnts) +//! + +use crate::errors::Result; +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 clnned +impl Clone for HeaderValue +where + T: Clone, +{ + fn clone(&self) -> Self { + HeaderValue { + value: self.value.clone(), + comment: self.comment.clone(), + } + } +} + +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 + pub fn map(self, f: F) -> HeaderValue + where + F: FnOnce(T) -> U, + { + HeaderValue { + value: f(self.value), + comment: self.comment, + } + } + + /// Monadic "bind" for [`HeaderValue`] + pub fn and_then(self, f: F) -> Result> + where + F: FnOnce(T) -> Result, + { + match f(self.value) { + Ok(value) => Ok(HeaderValue { + value, + comment: self.comment, + }), + Err(e) => Err(e), + } + } +} diff --git a/fitsio/src/headers.rs b/fitsio/src/headers/mod.rs similarity index 88% rename from fitsio/src/headers.rs rename to fitsio/src/headers/mod.rs index 2abc5302..d24a77cf 100644 --- a/fitsio/src/headers.rs +++ b/fitsio/src/headers/mod.rs @@ -4,101 +4,13 @@ use crate::fitsfile::FitsFile; use crate::longnam::*; use crate::types::DataType; use std::ffi; -use std::fmt::Debug; use std::ptr; -// FLEN_VALUE -const MAX_VALUE_LENGTH: usize = 71; -// FLEN_COMMENT -const MAX_COMMENT_LENGTH: usize = 73; +mod constants; +mod header_value; -/// 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 std::fmt::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 clnned -impl Clone for HeaderValue -where - T: Clone, -{ - fn clone(&self) -> Self { - HeaderValue { - value: self.value.clone(), - comment: self.comment.clone(), - } - } -} - -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 - pub fn map(self, f: F) -> HeaderValue - where - F: FnOnce(T) -> U, - { - HeaderValue { - value: f(self.value), - comment: self.comment, - } - } - - /// Monadic "bind" for [`HeaderValue`] - pub fn and_then(self, f: F) -> Result> - where - F: FnOnce(T) -> Result, - { - match f(self.value) { - Ok(value) => Ok(HeaderValue { - value, - comment: self.comment, - }), - Err(e) => Err(e), - } - } -} +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 diff --git a/fitsio/src/lib.rs b/fitsio/src/lib.rs index 357293ea..0ce43301 100644 --- a/fitsio/src/lib.rs +++ b/fitsio/src/lib.rs @@ -1046,6 +1046,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; From 7a2d6d181b92b77d448aec5e10e1c198a7188214 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Wed, 24 Jul 2024 21:29:38 +0100 Subject: [PATCH 15/18] Add coverage with doctests --- fitsio/src/headers/header_value.rs | 48 +++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/fitsio/src/headers/header_value.rs b/fitsio/src/headers/header_value.rs index a4835dbd..f75134aa 100644 --- a/fitsio/src/headers/header_value.rs +++ b/fitsio/src/headers/header_value.rs @@ -1,7 +1,6 @@ //! Header values (values + comemnts) //! -use crate::errors::Result; use std::fmt::Debug; use super::ReadsKey; @@ -40,6 +39,18 @@ where } /// Allow `HeaderValue` to be clnned +/// +/// ```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, @@ -52,6 +63,13 @@ where } } +/// 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, @@ -69,6 +87,12 @@ 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, @@ -80,16 +104,20 @@ where } /// Monadic "bind" for [`HeaderValue`] - pub fn and_then(self, f: F) -> Result> + /// ```rust + /// # use fitsio::headers::HeaderValue; + /// let hv = HeaderValue { value: 1, comment: None }; + /// let hv2 = hv.and_then(|value| HeaderValue { + /// value: value * 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) -> Result, + F: FnOnce(T) -> HeaderValue, { - match f(self.value) { - Ok(value) => Ok(HeaderValue { - value, - comment: self.comment, - }), - Err(e) => Err(e), - } + f(self.value) } } From d0417a26381f0a08df2ad0dec0a1f68f6992db1a Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Wed, 24 Jul 2024 21:51:48 +0100 Subject: [PATCH 16/18] Correct monadic bind for HeaderValue --- fitsio/src/headers/header_value.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fitsio/src/headers/header_value.rs b/fitsio/src/headers/header_value.rs index f75134aa..a999a4f1 100644 --- a/fitsio/src/headers/header_value.rs +++ b/fitsio/src/headers/header_value.rs @@ -107,8 +107,8 @@ where /// ```rust /// # use fitsio::headers::HeaderValue; /// let hv = HeaderValue { value: 1, comment: None }; - /// let hv2 = hv.and_then(|value| HeaderValue { - /// value: value * 2, + /// let hv2 = hv.and_then(|v| HeaderValue { + /// value: v * 2, /// comment: Some("ok".to_string()), /// }); /// assert_eq!(hv2.value, 2); From 57c1c7f2a56710205f94aac85480e668c6a5eb7c Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Fri, 26 Jul 2024 14:18:00 +0100 Subject: [PATCH 17/18] Update main documentation --- fitsio/src/lib.rs | 33 +++++++++++++++++++++++++++++---- fitsio/src/ndarray_compat.rs | 1 - 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/fitsio/src/lib.rs b/fitsio/src/lib.rs index 0ce43301..fdc40af0 100644 --- a/fitsio/src/lib.rs +++ b/fitsio/src/lib.rs @@ -387,15 +387,32 @@ 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> { +# 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> { @@ -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 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]], From 6c7fc37d4f429868d52408557856450b6a6add81 Mon Sep 17 00:00:00 2001 From: Simon Walker Date: Fri, 26 Jul 2024 14:20:39 +0100 Subject: [PATCH 18/18] Fix docstrings --- fitsio/src/headers/header_value.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fitsio/src/headers/header_value.rs b/fitsio/src/headers/header_value.rs index a999a4f1..3a3ea47a 100644 --- a/fitsio/src/headers/header_value.rs +++ b/fitsio/src/headers/header_value.rs @@ -14,7 +14,7 @@ pub struct HeaderValue { pub comment: Option, } -// Allow printing of `HeaderValue`s +/// Allow printing of `HeaderValue`s impl Debug for HeaderValue where T: Debug, @@ -27,8 +27,8 @@ where } } -// Allow comparing of `HeaderValue`'s where the `value` is equatable -// so that e.g. `HeaderValue` can be compared to `f64` +/// 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, @@ -38,7 +38,7 @@ where } } -/// Allow `HeaderValue` to be clnned +/// Allow `HeaderValue` to be cloned /// /// ```rust /// # use fitsio::headers::HeaderValue;