rsfitsio/
lib.rs

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