rsfitsio/
lib.rs

1// These are required for printf in Relibc
2#![feature(c_variadic)]
3#![allow(
4    non_camel_case_types,
5    non_snake_case,
6    dead_code,
7    unused_variables,
8    unused_assignments,
9    clippy::missing_safety_doc,
10    unreachable_code,
11    clippy::too_many_arguments,
12    clippy::needless_range_loop,
13    clippy::manual_range_contains
14)]
15#![deny(deprecated)]
16
17pub mod c_types;
18pub mod helpers;
19
20pub mod aliases;
21pub mod buffers;
22pub mod checksum;
23pub mod edithdu;
24pub mod fitsio;
25pub mod fitsio2;
26pub mod scalnull;
27
28//#[cfg(feature = "bzip2")]
29//mod bzip2;
30
31pub mod cfileio;
32pub mod drvrfile;
33pub mod drvrgsiftp;
34pub mod drvrmem;
35pub mod drvrnet;
36
37#[cfg(feature = "shared_mem")]
38pub mod drvrsmem;
39
40pub mod editcol;
41pub mod eval_defs;
42pub mod eval_f;
43pub mod eval_l;
44pub mod eval_y;
45pub mod fits_hcompress;
46pub mod fits_hdecompress;
47pub mod fitscore;
48pub mod getcol;
49pub mod getcolb;
50pub mod getcold;
51pub mod getcole;
52pub mod getcoli;
53pub mod getcolj;
54pub mod getcolk;
55pub mod getcoll;
56pub mod getcols;
57pub mod getcolsb;
58pub mod getcolui;
59pub mod getcoluj;
60pub mod getcoluk;
61pub mod getkey;
62pub mod group;
63pub mod grparser;
64pub mod histo;
65pub mod imcompress;
66pub mod iraffits;
67pub mod modkey;
68pub mod putcol;
69pub mod putcolb;
70pub mod putcold;
71pub mod putcole;
72pub mod putcoli;
73pub mod putcolj;
74pub mod putcolk;
75pub mod putcoll;
76pub mod putcols;
77pub mod putcolsb;
78pub mod putcolu;
79pub mod putcolui;
80pub mod putcoluj;
81pub mod putcoluk;
82pub mod putkey;
83pub mod quantize;
84pub mod region;
85pub mod relibc;
86pub mod simplerng;
87pub mod swapproc;
88pub mod wcssub;
89pub mod wcsutil;
90pub mod wrappers;
91pub mod zcompress;
92pub mod zuncompress;
93
94use std::{
95    ffi::{CStr, CString},
96    marker::PhantomData,
97    str::FromStr,
98    sync::{Mutex, MutexGuard},
99};
100
101use bytemuck::cast_slice;
102use fitsio::{
103    FLEN_VALUE, LONGLONG, TBIT, TBYTE, TCOMPLEX, TDBLCOMPLEX, TDOUBLE, TFLOAT, TINT, TLOGICAL,
104    TLONG, TLONGLONG, TSBYTE, TSHORT, TSTRING, TUINT, TULONG, TULONGLONG, TUSHORT, ULONGLONG,
105};
106
107use crate::c_types::*;
108
109pub(crate) static MUTEX_LOCK: Mutex<bool> = Mutex::new(false);
110
111pub(crate) fn FFLOCK<'a>() -> MutexGuard<'a, bool> {
112    MUTEX_LOCK.lock().unwrap()
113}
114
115pub(crate) fn FFUNLOCK(p: MutexGuard<'_, bool>) {
116    drop(p);
117}
118
119pub trait ToRaw {
120    fn as_raw_mut(&mut self) -> *mut Self;
121}
122
123pub trait AsMutPtr<T> {
124    fn as_mut_ptr(&self) -> *mut T;
125}
126
127impl<T> AsMutPtr<T> for Option<&mut [T]> {
128    fn as_mut_ptr(&self) -> *mut T {
129        match self {
130            Some(v) => v.as_ptr() as *mut T, // UNSAFE
131            None => std::ptr::null_mut(),
132        }
133    }
134}
135
136#[inline(always)]
137pub fn bb(n: u8) -> c_char {
138    n as c_char
139}
140
141#[macro_export]
142macro_rules! int_snprintf {
143    ($dst:expr, $len:expr, $($arg:tt)*) => {
144        {
145            let s = format!($($arg)*);
146            let s_bytes = s.as_bytes();
147            let mut s_len = s_bytes.len();
148
149            s_len = cmp::min($len-1, s_len);
150
151            let w = cast_slice_mut::<c_char, u8>(&mut $dst[..s_len]);
152            w.copy_from_slice(&s_bytes[..s_len]);
153            $dst[s_len] = 0; // null-terminate
154
155            s_len as isize
156        }
157    };
158}
159
160#[macro_export]
161macro_rules! slice_to_str {
162    ($e:expr) => {
163        CStr::from_bytes_until_nul(cast_slice($e))
164            .unwrap()
165            .to_str()
166            .unwrap()
167    };
168}
169
170#[macro_export]
171macro_rules! cs {
172    ($e: expr) => {
173        cast_slice($e.to_bytes_with_nul())
174    };
175}
176
177#[macro_export]
178macro_rules! nullable_slice_cstr {
179    ($e: ident) => {
180        let $e: Option<&[c_char]> = match $e.is_null() {
181            true => None,
182            false => Some(cast_slice(CStr::from_ptr($e).to_bytes_with_nul())),
183        };
184    };
185}
186
187#[macro_export]
188macro_rules! nullable_slice_cstr_mut {
189    ($e: ident) => {
190        let mut $e: Option<&mut [c_char]> = match $e.is_null() {
191            true => None,
192            false => {
193                let _c = CStr::from_ptr($e).to_bytes_with_nul();
194                let _l = _c.len();
195
196                Some(slice::from_raw_parts_mut($e, _l))
197            }
198        };
199    };
200}
201
202#[macro_export]
203macro_rules! raw_to_slice {
204    ($e: ident) => {
205        let $e: &[c_char] = cast_slice(CStr::from_ptr($e).to_bytes_with_nul());
206    };
207}
208
209pub(crate) struct TKeywords<'a> {
210    tfields: c_int,              /* I - number of columns in the table           */
211    ttype: *const *const c_char, /* I - name of each column                      */
212    tform: *const *const c_char, /* I - value of TFORMn keyword for each column  */
213    tunit: *const *const c_char, /* I - value of TUNITn keyword for each column  */
214    marker: PhantomData<&'a ()>,
215}
216
217impl<'a> TKeywords<'a> {
218    pub fn new(
219        tfields: c_int,
220        ttype: *const *const c_char,
221        tform: *const *const c_char,
222        tunit: *const *const c_char,
223    ) -> Self {
224        TKeywords {
225            tfields,
226            ttype,
227            tform,
228            tunit,
229            marker: PhantomData,
230        }
231    }
232
233    pub unsafe fn tkeywords_to_vecs(
234        &'a self,
235    ) -> (
236        Vec<Option<&'a [c_char]>>,
237        Vec<&'a [c_char]>,
238        Option<Vec<Option<&'a [c_char]>>>,
239    ) {
240        unsafe {
241            // Convert ttype to rust types
242            let ttype = core::slice::from_raw_parts(self.ttype, self.tfields as usize);
243            let mut v_ttype = Vec::new();
244
245            for item in ttype {
246                let ttype_item = if item.is_null() {
247                    None
248                } else {
249                    Some(cast_slice(CStr::from_ptr(*item).to_bytes_with_nul()))
250                };
251                v_ttype.push(ttype_item);
252            }
253
254            // Convert tform to rust types
255            let tform = core::slice::from_raw_parts(self.tform, self.tfields as usize);
256            let mut v_tform = Vec::new();
257
258            for item in tform {
259                let tform_item = cast_slice(CStr::from_ptr(*item).to_bytes_with_nul());
260                v_tform.push(tform_item);
261            }
262
263            // Convert tunit to rust types
264            let mut v_tunit = Vec::new();
265            let out_tunit = if self.tunit.is_null() {
266                None
267            } else {
268                let tunit = core::slice::from_raw_parts(self.tunit, self.tfields as usize);
269
270                for item in tunit {
271                    let tunit_item = if item.is_null() {
272                        None
273                    } else {
274                        Some(cast_slice(CStr::from_ptr(*item).to_bytes_with_nul()))
275                    };
276                    v_tunit.push(tunit_item);
277                }
278                Some(v_tunit)
279            };
280
281            (v_ttype, v_tform, out_tunit)
282        }
283    }
284}
285
286pub(crate) fn calculate_subsection_length(blc: &[c_long], trc: &[c_long], inc: &[c_long]) -> usize {
287    assert!(blc.len() == trc.len() && blc.len() == inc.len());
288
289    let len = blc.len();
290    let mut acc: usize = 1;
291    for ii in 0..len {
292        acc *= ((trc[ii] - blc[ii]) / inc[ii] + 1) as usize; // WARNING: This also could be wrong
293    }
294    acc
295}
296
297pub(crate) fn calculate_subsection_length_unit(blc: &[c_long], trc: &[c_long]) -> usize {
298    assert!(blc.len() == trc.len());
299
300    let len = blc.len();
301    let mut acc: usize = 1;
302    for ii in 0..len {
303        acc *= ((trc[ii] - blc[ii]) + 1) as usize; // WARNING: This also could be wrong
304    }
305    acc
306}
307
308pub(crate) fn vecs_to_slices<T>(vecs: &[Vec<T>]) -> Vec<&[T]> {
309    vecs.iter().map(Vec::as_slice).collect()
310}
311
312pub(crate) fn vecs_to_slices_mut<T>(vecs: &mut [Vec<T>]) -> Vec<&mut [T]> {
313    vecs.iter_mut().map(Vec::as_mut_slice).collect()
314}
315
316#[derive(Debug, PartialEq, Clone, Copy)]
317pub enum NullCheckType {
318    None = 0,         // No null checking
319    SetPixel = 1,     // Set undefined pixels as a null value
320    SetNullArray = 2, // Set a null array = 1 for undefined pixels
321}
322
323#[derive(Debug, PartialEq, Clone)]
324pub enum NullValue {
325    Float(f32),
326    Double(f64),
327    Long(c_long),
328    ULong(c_ulong),
329    LONGLONG(LONGLONG),
330    ULONGLONG(ULONGLONG),
331    Int(c_int),
332    UInt(c_uint),
333    Short(c_short),
334    UShort(c_ushort),
335    Byte(i8),
336    UByte(c_uchar),
337    Logical(c_char),
338    String(CString),
339}
340
341impl NullValue {
342    pub fn get_value_as_f64(&self) -> f64 {
343        match self {
344            NullValue::Float(v) => *v as f64,
345            NullValue::Double(v) => *v,
346            NullValue::Long(v) => *v as f64,
347            NullValue::ULong(v) => *v as f64,
348            NullValue::LONGLONG(v) => *v as f64,
349            NullValue::ULONGLONG(v) => *v as f64,
350            NullValue::Int(v) => *v as f64,
351            NullValue::UInt(v) => *v as f64,
352            NullValue::Short(v) => *v as f64,
353            NullValue::UShort(v) => *v as f64,
354            NullValue::Byte(v) => *v as f64,
355            NullValue::UByte(v) => *v as f64,
356            NullValue::Logical(v) => *v as f64,
357            _ => 0.0,
358        }
359    }
360
361    pub fn from_raw_ptr(datatype: c_int, value: *const c_void) -> Option<Self> {
362        if value.is_null() {
363            return None;
364        }
365
366        match datatype {
367            TFLOAT => Some(NullValue::Float(unsafe { *(value as *const f32) })),
368            TDOUBLE => Some(NullValue::Double(unsafe { *(value as *const f64) })),
369            TLONG => Some(NullValue::Long(unsafe { *(value as *const c_long) })),
370            TULONG => Some(NullValue::ULong(unsafe { *(value as *const c_ulong) })),
371            TLONGLONG => Some(NullValue::LONGLONG(unsafe { *(value as *const LONGLONG) })),
372            TULONGLONG => Some(NullValue::ULONGLONG(unsafe {
373                *(value as *const ULONGLONG)
374            })),
375            TINT => Some(NullValue::Int(unsafe { *(value as *const c_int) })),
376            TUINT => Some(NullValue::UInt(unsafe { *(value as *const c_uint) })),
377            TSHORT => Some(NullValue::Short(unsafe { *(value as *const c_short) })),
378            TUSHORT => Some(NullValue::UShort(unsafe { *(value as *const c_ushort) })),
379            TBYTE => Some(NullValue::UByte(unsafe { *(value as *const c_uchar) })),
380            TSBYTE => Some(NullValue::Byte(unsafe { *(value as *const i8) })),
381            TLOGICAL => Some(NullValue::Logical(unsafe { *(value as *const c_char) })),
382            TSTRING => {
383                let cstr = unsafe { CStr::from_ptr(value as *const c_char) };
384                Some(NullValue::String(cstr.to_owned()))
385            }
386            _ => None, // Don't panic here, will be handled by the caller
387        }
388    }
389}
390
391#[derive(Debug, PartialEq)]
392pub enum KeywordDatatype<'a> {
393    TBYTE(&'a c_uchar),
394    TSBYTE(&'a c_char),
395    TSHORT(&'a c_short),
396    TUSHORT(&'a c_ushort),
397    TINT(&'a c_int),
398    TUINT(&'a c_uint),
399    TLONG(&'a c_long),
400    TULONG(&'a c_ulong),
401    TFLOAT(&'a f32),
402    TDOUBLE(&'a f64),
403    TSTRING(&'a [c_char]),
404    TLOGICAL(&'a c_int),
405    TCOMPLEX(&'a [f32; 2]),
406    TDBLCOMPLEX(&'a [f64; 2]),
407    TULONGLONG(&'a ULONGLONG),
408    TLONGLONG(&'a LONGLONG),
409    INVALID(c_int),
410}
411
412impl KeywordDatatype<'_> {
413    pub fn from_datatype(datatype: c_int, value: *const c_void) -> Self {
414        match datatype {
415            TBYTE => KeywordDatatype::TBYTE(unsafe { &*(value as *const c_uchar) }),
416            TSBYTE => KeywordDatatype::TSBYTE(unsafe { &*(value as *const c_char) }),
417            TSHORT => KeywordDatatype::TSHORT(unsafe { &*(value as *const c_short) }),
418            TUSHORT => KeywordDatatype::TUSHORT(unsafe { &*(value as *const c_ushort) }),
419            TINT => KeywordDatatype::TINT(unsafe { &*(value as *const c_int) }),
420            TUINT => KeywordDatatype::TUINT(unsafe { &*(value as *const c_uint) }),
421            TLONG => KeywordDatatype::TLONG(unsafe { &*(value as *const c_long) }),
422            TULONG => KeywordDatatype::TULONG(unsafe { &*(value as *const c_ulong) }),
423            TFLOAT => KeywordDatatype::TFLOAT(unsafe { &*(value as *const f32) }),
424            TDOUBLE => KeywordDatatype::TDOUBLE(unsafe { &*(value as *const f64) }),
425            TSTRING => KeywordDatatype::TSTRING(unsafe {
426                cast_slice(CStr::from_ptr(value as *const c_char).to_bytes_with_nul())
427            }),
428            TLOGICAL => KeywordDatatype::TLOGICAL(unsafe { &*(value as *const c_int) }),
429            TCOMPLEX => KeywordDatatype::TCOMPLEX(unsafe { &*(value as *const [f32; 2]) }),
430            TDBLCOMPLEX => KeywordDatatype::TDBLCOMPLEX(unsafe { &*(value as *const [f64; 2]) }),
431            TULONGLONG => KeywordDatatype::TULONGLONG(unsafe { &*(value as *const ULONGLONG) }),
432            TLONGLONG => KeywordDatatype::TLONGLONG(unsafe { &*(value as *const LONGLONG) }),
433            _ => KeywordDatatype::INVALID(datatype),
434        }
435    }
436
437    pub fn to_datatype_code(&self) -> c_int {
438        match self {
439            KeywordDatatype::TBYTE(_) => TBYTE,
440            KeywordDatatype::TSBYTE(_) => TSBYTE,
441            KeywordDatatype::TSHORT(_) => TSHORT,
442            KeywordDatatype::TUSHORT(_) => TUSHORT,
443            KeywordDatatype::TINT(_) => TINT,
444            KeywordDatatype::TUINT(_) => TUINT,
445            KeywordDatatype::TLONG(_) => TLONG,
446            KeywordDatatype::TULONG(_) => TULONG,
447            KeywordDatatype::TFLOAT(_) => TFLOAT,
448            KeywordDatatype::TDOUBLE(_) => TDOUBLE,
449            KeywordDatatype::TSTRING(_) => TSTRING,
450            KeywordDatatype::TLOGICAL(_) => TLOGICAL,
451            KeywordDatatype::TCOMPLEX(_) => TCOMPLEX,
452            KeywordDatatype::TDBLCOMPLEX(_) => TDBLCOMPLEX,
453            KeywordDatatype::TULONGLONG(_) => TULONGLONG,
454            KeywordDatatype::TLONGLONG(_) => TLONGLONG,
455            KeywordDatatype::INVALID(x) => *x,
456        }
457    }
458}
459
460#[derive(Debug, PartialEq)]
461pub enum KeywordDatatypeMut<'a> {
462    TBYTE(&'a mut c_uchar),
463    TSBYTE(&'a mut c_char),
464    TSHORT(&'a mut c_short),
465    TUSHORT(&'a mut c_ushort),
466    TINT(&'a mut c_int),
467    TUINT(&'a mut c_uint),
468    TLONG(&'a mut c_long),
469    TULONG(&'a mut c_ulong),
470    TFLOAT(&'a mut f32),
471    TDOUBLE(&'a mut f64),
472    TSTRING(&'a mut [c_char; FLEN_VALUE]),
473    TLOGICAL(&'a mut c_int),
474    TCOMPLEX(&'a mut [f32; 2]),
475    TDBLCOMPLEX(&'a mut [f64; 2]),
476    TULONGLONG(&'a mut ULONGLONG),
477    TLONGLONG(&'a mut LONGLONG),
478    INVALID(c_int),
479}
480
481impl KeywordDatatypeMut<'_> {
482    pub fn from_datatype(datatype: c_int, value: *mut c_void) -> Self {
483        match datatype {
484            TBYTE => KeywordDatatypeMut::TBYTE(unsafe { &mut *(value as *mut c_uchar) }),
485            TSBYTE => KeywordDatatypeMut::TSBYTE(unsafe { &mut *(value as *mut c_char) }),
486            TSHORT => KeywordDatatypeMut::TSHORT(unsafe { &mut *(value as *mut c_short) }),
487            TUSHORT => KeywordDatatypeMut::TUSHORT(unsafe { &mut *(value as *mut c_ushort) }),
488            TINT => KeywordDatatypeMut::TINT(unsafe { &mut *(value as *mut c_int) }),
489            TUINT => KeywordDatatypeMut::TUINT(unsafe { &mut *(value as *mut c_uint) }),
490            TLONG => KeywordDatatypeMut::TLONG(unsafe { &mut *(value as *mut c_long) }),
491            TULONG => KeywordDatatypeMut::TULONG(unsafe { &mut *(value as *mut c_ulong) }),
492            TFLOAT => KeywordDatatypeMut::TFLOAT(unsafe { &mut *(value as *mut f32) }),
493            TDOUBLE => KeywordDatatypeMut::TDOUBLE(unsafe { &mut *(value as *mut f64) }),
494            TSTRING => {
495                KeywordDatatypeMut::TSTRING(unsafe { &mut *(value as *mut [c_char; FLEN_VALUE]) })
496            }
497            TLOGICAL => KeywordDatatypeMut::TLOGICAL(unsafe { &mut *(value as *mut c_int) }),
498            TCOMPLEX => KeywordDatatypeMut::TCOMPLEX(unsafe { &mut *(value as *mut [f32; 2]) }),
499            TDBLCOMPLEX => {
500                KeywordDatatypeMut::TDBLCOMPLEX(unsafe { &mut *(value as *mut [f64; 2]) })
501            }
502            TULONGLONG => {
503                KeywordDatatypeMut::TULONGLONG(unsafe { &mut *(value as *mut ULONGLONG) })
504            }
505            TLONGLONG => KeywordDatatypeMut::TLONGLONG(unsafe { &mut *(value as *mut LONGLONG) }),
506            _ => KeywordDatatypeMut::INVALID(datatype),
507        }
508    }
509
510    pub fn to_datatype_code(&self) -> c_int {
511        match self {
512            KeywordDatatypeMut::TBYTE(_) => TBYTE,
513            KeywordDatatypeMut::TSBYTE(_) => TSBYTE,
514            KeywordDatatypeMut::TSHORT(_) => TSHORT,
515            KeywordDatatypeMut::TUSHORT(_) => TUSHORT,
516            KeywordDatatypeMut::TINT(_) => TINT,
517            KeywordDatatypeMut::TUINT(_) => TUINT,
518            KeywordDatatypeMut::TLONG(_) => TLONG,
519            KeywordDatatypeMut::TULONG(_) => TULONG,
520            KeywordDatatypeMut::TFLOAT(_) => TFLOAT,
521            KeywordDatatypeMut::TDOUBLE(_) => TDOUBLE,
522            KeywordDatatypeMut::TSTRING(_) => TSTRING,
523            KeywordDatatypeMut::TLOGICAL(_) => TLOGICAL,
524            KeywordDatatypeMut::TCOMPLEX(_) => TCOMPLEX,
525            KeywordDatatypeMut::TDBLCOMPLEX(_) => TDBLCOMPLEX,
526            KeywordDatatypeMut::TULONGLONG(_) => TULONGLONG,
527            KeywordDatatypeMut::TLONGLONG(_) => TLONGLONG,
528            KeywordDatatypeMut::INVALID(x) => *x,
529        }
530    }
531}
532
533pub(crate) fn bytes_per_datatype(datatype: c_int) -> Option<usize> {
534    match datatype {
535        TBIT => Some(1),
536        TBYTE => Some(1),
537        TLOGICAL => Some(1),
538        TSBYTE => Some(1),
539        TUSHORT => Some(2),
540        TSHORT => Some(2),
541        TUINT => Some(4),
542        TINT => Some(4),
543        TULONG => Some(std::mem::size_of::<c_ulong>()),
544        TLONG => Some(std::mem::size_of::<c_long>()),
545        TULONGLONG => Some(8),
546        TLONGLONG => Some(8),
547        TFLOAT => Some(4),
548        TDOUBLE => Some(8),
549        TCOMPLEX => Some(8),
550        TDBLCOMPLEX => Some(16),
551        TSTRING => Some(std::mem::size_of::<usize>()), // pointer size
552        _ => None,
553    }
554}
555
556//https://stackoverflow.com/questions/65601579/parse-an-integer-ignoring-any-non-numeric-suffix
557fn atoi<F: FromStr>(input: &str) -> Result<F, <F as FromStr>::Err> {
558    let input = input.trim();
559    let i = input
560        .find(|c: char| !c.is_numeric() && c != '-' && c != '+')
561        .unwrap_or(input.len());
562    input[..i].parse::<F>()
563}
564
565// https://stackoverflow.com/questions/65264069/alignment-of-floating-point-numbers-printed-in-scientific-notation
566fn fmt_f64(num: f64, precision: usize, exp_pad: usize) -> String {
567    let mut num = format!("{num:.precision$E}");
568    // Safe to `unwrap` as `num` is guaranteed to contain `'e'`
569    let exp = num.split_off(num.find('E').unwrap());
570
571    let (sign, exp) = if exp.starts_with("E-") {
572        ('-', &exp[2..])
573    } else {
574        ('+', &exp[1..])
575    };
576    num.push_str(&format!("E{sign}{exp:0>exp_pad$}"));
577
578    num.to_string()
579}
580
581const WB_MODE: *const c_char = c"wb".as_ptr().cast::<c_char>();
582const RB_MODE: *const c_char = c"rb".as_ptr().cast::<c_char>();
583
584#[cfg(not(target_os = "windows"))]
585unsafe extern "C" {
586
587    pub unsafe static mut stdin: *mut FILE;
588
589    pub unsafe static mut stdout: *mut FILE;
590
591    pub unsafe static mut stderr: *mut FILE;
592}
593
594#[cfg(windows)]
595unsafe extern "C" {
596    pub unsafe fn __acrt_iob_func(idx: libc::c_uint) -> *mut FILE;
597}
598
599#[macro_export]
600#[cfg(not(target_os = "windows"))]
601macro_rules! STDIN {
602    () => {
603        $crate::stdin
604    };
605}
606
607#[macro_export]
608#[cfg(windows)]
609macro_rules! STDIN {
610    () => {
611        $crate::__acrt_iob_func(0)
612    };
613}
614
615#[macro_export]
616#[cfg(not(target_os = "windows"))]
617macro_rules! STDOUT {
618    () => {
619        $crate::stdout
620    };
621}
622
623#[macro_export]
624#[cfg(windows)]
625macro_rules! STDOUT {
626    () => {
627        $crate::__acrt_iob_func(1)
628    };
629}
630
631#[macro_export]
632#[cfg(not(target_os = "windows"))]
633macro_rules! STDERR {
634    () => {
635        $crate::stderr
636    };
637}
638
639#[macro_export]
640#[cfg(windows)]
641macro_rules! STDERR {
642    () => {
643        $crate::__acrt_iob_func(2)
644    };
645}
646
647#[cfg(test)]
648mod tests {
649    use std::{ffi::CString, slice};
650
651    use crate::cs;
652    use bytemuck::cast_slice;
653    use cfileio::{ffclos_safer, ffinit_safer};
654
655    use putkey::ffcrim_safer;
656    use tempfile::Builder;
657
658    use crate::{
659        fitsio::{USHORT_IMG, fitsfile},
660        helpers::testhelpers::with_temp_file,
661    };
662
663    use super::*;
664
665    use crate::aliases::rust_api::{fits_update_key, fits_write_img};
666
667    #[test]
668    fn test_write_image() {
669        unsafe {
670            with_temp_file(|filename| {
671                let bitpix = USHORT_IMG;
672                let naxis = 2;
673                const NAXES: [c_long; 2] = [300, 200];
674                let mut storage: [[u16; NAXES[0] as usize]; NAXES[1] as usize] =
675                    [[0; NAXES[0] as usize]; NAXES[1] as usize];
676                let mut fptr: Option<Box<fitsfile>> = None;
677                let mut status = 0;
678
679                let tempfile = Builder::new()
680                    .prefix("my-temporary-note")
681                    .suffix(".fits")
682                    .tempfile()
683                    .unwrap();
684
685                let tdir = Builder::new().prefix("rsfitsio-").tempdir().unwrap();
686                let abc = Builder::new().prefix("prefix").tempfile();
687                let tdir_path = tdir.path();
688                let filename = tdir_path.join("test.fits");
689
690                let filename_str = filename.to_str().expect("cannot create string filename");
691                let filename_cstr = CString::new(filename_str).unwrap();
692
693                status = ffinit_safer(
694                    &mut fptr,
695                    cast_slice(filename_cstr.as_bytes_with_nul()),
696                    &mut status,
697                );
698                assert_eq!(status, 0);
699
700                let mut fptr = fptr.unwrap();
701
702                status = ffcrim_safer(&mut fptr, bitpix, naxis, &NAXES, &mut status);
703                assert_eq!(status, 0);
704
705                for jj in 0..(NAXES[1] as usize) {
706                    for ii in 0..(NAXES[0] as usize) {
707                        storage[jj][ii] = (ii + jj) as u16;
708                    }
709                }
710
711                let fpixel = 1; /* first pixel to write      */
712                let nelements = NAXES[0] * NAXES[1]; /* number of pixels to write */
713
714                /* write the array of unsigned integers to the FITS file */
715                let s = slice::from_raw_parts(
716                    storage.as_ptr() as *mut u16,
717                    (NAXES[0] * NAXES[1]) as usize,
718                );
719                fits_write_img(
720                    &mut fptr,
721                    TUSHORT,
722                    fpixel,
723                    nelements as LONGLONG,
724                    cast_slice(s),
725                    &mut status,
726                );
727                assert_eq!(status, 0);
728
729                /* write another optional keyword to the header */
730                /* Note that the ADDRESS of the value is passed in the routine */
731                let exposure = 1500;
732                fits_update_key(
733                    &mut fptr,
734                    KeywordDatatype::TLONG(&exposure),
735                    cs!(c"EXPOSURE"),
736                    Some(cs!(c"Total Exposure Time")),
737                    &mut status,
738                );
739                assert_eq!(status, 0);
740
741                ffclos_safer(fptr, &mut status); /* close the file */
742                assert_eq!(status, 0);
743            });
744        }
745    }
746}