1 /*********************************************************************
2 Fits - View and manipulate FITS extensions and/or headers.
3 Fits is part of GNU Astronomy Utilities (Gnuastro) package.
4
5 Original author:
6 Mohammad Akhlaghi <mohammad@akhlaghi.org>
7 Contributing author(s):
8 Copyright (C) 2016-2021, Free Software Foundation, Inc.
9
10 Gnuastro is free software: you can redistribute it and/or modify it
11 under the terms of the GNU General Public License as published by the
12 Free Software Foundation, either version 3 of the License, or (at your
13 option) any later version.
14
15 Gnuastro is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with Gnuastro. If not, see <http://www.gnu.org/licenses/>.
22 **********************************************************************/
23 #include <config.h>
24
25 #include <argp.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <stdio.h>
29 #include <string.h>
30
31 #include <gnuastro/wcs.h>
32 #include <gnuastro/fits.h>
33
34 #include <gnuastro-internal/options.h>
35 #include <gnuastro-internal/checkset.h>
36 #include <gnuastro-internal/fixedstringmacros.h>
37
38 #include "main.h"
39
40 #include "ui.h"
41 #include "authors-cite.h"
42
43
44
45
46
47 /**************************************************************/
48 /********* Argp necessary global entities ************/
49 /**************************************************************/
50 /* Definition parameters for the Argp: */
51 const char *
52 argp_program_version = PROGRAM_STRING "\n"
53 GAL_STRINGS_COPYRIGHT
54 "\n\nWritten/developed by "PROGRAM_AUTHORS;
55
56 const char *
57 argp_program_bug_address = PACKAGE_BUGREPORT;
58
59 static char
60 args_doc[] = "ASTRdata";
61
62 const char
63 doc[] = GAL_STRINGS_TOP_HELP_INFO PROGRAM_NAME" allows you to view and "
64 "manipulate (add, delete, or modify) FITS extensions (or HDUs) and FITS "
65 "header keywords within one extension.\n"
66 GAL_STRINGS_MORE_HELP_INFO
67 /* After the list of options: */
68 "\v"
69 PACKAGE_NAME" home page: "PACKAGE_URL;
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 /**************************************************************/
91 /********* Initialize & Parse command-line **************/
92 /**************************************************************/
93 static void
ui_initialize_options(struct fitsparams * p,struct argp_option * program_options,struct argp_option * gal_commonopts_options)94 ui_initialize_options(struct fitsparams *p,
95 struct argp_option *program_options,
96 struct argp_option *gal_commonopts_options)
97 {
98 size_t i;
99 struct gal_options_common_params *cp=&p->cp;
100
101 /* Set the necessary common parameters structure. */
102 cp->keep = 1;
103 cp->program_struct = p;
104 cp->poptions = program_options;
105 cp->program_name = PROGRAM_NAME;
106 cp->program_exec = PROGRAM_EXEC;
107 cp->program_bibtex = PROGRAM_BIBTEX;
108 cp->program_authors = PROGRAM_AUTHORS;
109 cp->coptions = gal_commonopts_options;
110
111 /* For clarity and non-zero initializations. */
112 p->mode = FITS_MODE_INVALID;
113
114 /* Modify common options. */
115 for(i=0; !gal_options_is_last(&cp->coptions[i]); ++i)
116 {
117 /* Select individually. */
118 switch(cp->coptions[i].key)
119 {
120 case GAL_OPTIONS_KEY_SEARCHIN:
121 case GAL_OPTIONS_KEY_IGNORECASE:
122 case GAL_OPTIONS_KEY_TYPE:
123 case GAL_OPTIONS_KEY_WCSLINEARMATRIX:
124 case GAL_OPTIONS_KEY_DONTDELETE:
125 case GAL_OPTIONS_KEY_LOG:
126 case GAL_OPTIONS_KEY_NUMTHREADS:
127 case GAL_OPTIONS_KEY_STDINTIMEOUT:
128 cp->coptions[i].flags=OPTION_HIDDEN;
129 break;
130
131 case GAL_OPTIONS_KEY_OUTPUT:
132 cp->coptions[i].doc="Output file name (only for writing HDUs).";
133 break;
134 }
135
136 /* Select by group. */
137 switch(cp->coptions[i].group)
138 {
139 case GAL_OPTIONS_GROUP_TESSELLATION:
140 cp->coptions[i].doc=NULL; /* Necessary to remove title. */
141 cp->coptions[i].flags=OPTION_HIDDEN;
142 break;
143 }
144 }
145 }
146
147
148
149
150
151 /* Parse a single option: */
152 error_t
parse_opt(int key,char * arg,struct argp_state * state)153 parse_opt(int key, char *arg, struct argp_state *state)
154 {
155 struct fitsparams *p = state->input;
156
157 /* Pass 'gal_options_common_params' into the child parser. */
158 state->child_inputs[0] = &p->cp;
159
160 /* In case the user incorrectly uses the equal sign (for example
161 with a short format or with space in the long format, then 'arg'
162 start with (if the short version was called) or be (if the long
163 version was called with a space) the equal sign. So, here we
164 check if the first character of arg is the equal sign, then the
165 user is warned and the program is stopped: */
166 if(arg && arg[0]=='=')
167 argp_error(state, "incorrect use of the equal sign ('='). For short "
168 "options, '=' should not be used and for long options, "
169 "there should be no space between the option, equal sign "
170 "and value");
171
172 /* Set the key to this option. */
173 switch(key)
174 {
175 /* Read the non-option tokens (arguments). */
176 case ARGP_KEY_ARG:
177 /* The user may give a shell variable that is empty! In that case
178 'arg' will be an empty string! We don't want to account for such
179 cases (and give a clear error that no input has been given). */
180 if(arg[0]!='\0')
181 {
182 if( gal_fits_file_recognized(arg) )
183 gal_list_str_add(&p->input, arg, 1);
184 else
185 argp_error(state, "%s is not a recognized FITS file", arg);
186 }
187 break;
188
189
190 /* This is an option, set its value. */
191 default:
192 return gal_options_set_from_key(key, arg, p->cp.poptions, &p->cp);
193 }
194
195 return 0;
196 }
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 /**************************************************************/
218 /*************** Sanity Check *******************/
219 /**************************************************************/
220 static void
ui_check_copykeys(struct fitsparams * p)221 ui_check_copykeys(struct fitsparams *p)
222 {
223 long read;
224 char *tailptr;
225 /* size_t group=0; */
226 char forl='f', *pt=p->copykeys;
227
228 /* For copykeys, an output filename is mandatory. */
229 if(p->cp.output==NULL || p->outhdu==NULL)
230 error(EXIT_FAILURE, 0, "an output FITS extension (in an existing "
231 "FITS file, specified with the '--output' and '--outhdu') are "
232 "mandatory for running '--copykeys'");
233
234 /* Initialize the values. */
235 p->copykeysrange[0]=p->copykeysrange[1]=GAL_BLANK_LONG;
236
237 /* Parse the string: 'forl': "first-or-last". */
238 while(*pt!='\0')
239 {
240 switch(*pt)
241 {
242 case ':':
243 forl='l';
244 ++pt;
245 break;
246 case '.':
247 error(EXIT_FAILURE, 0, "the numbers in the argument to "
248 "'--section' ('-s') have to be integers. You input "
249 "includes a float number: %s", p->copykeys);
250 break;
251 case ' ': case '\t':
252 ++pt;
253 break;
254
255 /* Numerical characters signify the start of a number, so we don't
256 need to increment the pointer and can just break out. */
257 case '0': case '1': case '2': case '3': case '4': case '5':
258 case '6': case '7': case '8': case '9': case '-':
259 break;
260
261 /* Identifier for next group of ranges. However, For the time
262 being, we just support one group. So we are commenting the break
263 here for it to follow onto default.
264 case ',':
265 ++group;
266 forl='f';
267 ++pt;
268 break;
269 */
270 default:
271 error(EXIT_FAILURE, 0, "value to '--copykeys' must only contain "
272 "integer numbers and these special characters between them: "
273 "':' when necessary. But it is '%s' (the first "
274 "non-acceptable character is '%c').\n", p->copykeys, *pt);
275 break;
276 }
277
278 /* Read the number: */
279 read=strtol(pt, &tailptr, 0);
280
281 /* Check the progress.
282 printf("\n\n------\n%c: %ld (%s)\n", *pt, read, tailptr);
283 */
284
285 /* Make sure if a number was read at all? */
286 if(tailptr==pt) continue; /* No number was read! */
287
288 /* Put it in the correct place. */
289 p->copykeysrange[ forl=='f' ? 0 : 1 ]=read;
290 pt=tailptr;
291 }
292
293 /* Basic sanity checks. */
294 if( p->copykeysrange[1]==GAL_BLANK_LONG )
295 error(EXIT_FAILURE, 0, "no ending keyword number given to '--copykeys'. "
296 "If you want to copy all the keywords after a certain one "
297 "(without worrying about how many there are), you can use '-1'.\n\n"
298 "For example if you want to copy all the keywords after the 20th, "
299 "you can use '--copykeys=20,-1'. Generally, you can use negative "
300 "numbers for the last keyword number to count from the end.");
301 if( p->copykeysrange[0]<=0 )
302 error(EXIT_FAILURE, 0, "the first number given to '--copykeys' must be "
303 "positive");
304 if( p->copykeysrange[1]>=0 && p->copykeysrange[0]>=p->copykeysrange[1] )
305 error(EXIT_FAILURE, 0, "the first number (%ld) given to '--copykeys' "
306 "must be smaller than the second (%ld)", p->copykeysrange[0],
307 p->copykeysrange[1]);
308
309 /* For a check:
310 printf("copykeys: %ld, %ld\n", p->copykeysrange[0], p->copykeysrange[1]);
311 exit(0);
312 */
313 }
314
315
316
317
318
319 /* Read and check ONLY the options. When arguments are involved, do the
320 check in 'ui_check_options_and_arguments'. */
321 static void
ui_read_check_only_options(struct fitsparams * p)322 ui_read_check_only_options(struct fitsparams *p)
323 {
324 int checkkeys;
325 uint8_t stdoutcheck=0;
326
327 /* If any of the keyword manipulation options are requested, then set the
328 mode flag to keyword-mode. */
329 if( p->date || p->comment || p->history || p->asis || p->keyvalue
330 || p->delete || p->rename || p->update || p->write || p->verify
331 || p->printallkeys || p->printkeynames || p->copykeys || p->datetosec
332 || p->wcscoordsys || p->wcsdistortion )
333 {
334 /* Check if a HDU is given. */
335 if(p->cp.hdu==NULL)
336 error(EXIT_FAILURE, 0, "a HDU (extension) is necessary for keyword "
337 "related options but none was defined. Please use the "
338 "'--hdu' (or '-h') option to select one");
339
340 /* If Copy keys has been given, read it and make sure its setup. */
341 if(p->copykeys)
342 ui_check_copykeys(p);
343
344 /* Keyword-related options that must be called alone. */
345 checkkeys = ( (p->keyvalue!=NULL)
346 + (p->datetosec!=NULL)
347 + (p->printkeynames!=0)
348 + (p->wcscoordsys!=NULL)
349 + (p->wcsdistortion!=NULL) );
350 if( ( checkkeys
351 && ( p->date || p->comment || p->history || p->asis
352 || p->delete || p->rename || p->update || p->write
353 || p->verify || p->printallkeys || p->copykeys ) )
354 || checkkeys>1 )
355 error(EXIT_FAILURE, 0, "'--keyvalue', '--datetosec', "
356 "'--wcscoordsys' and '--wcsdistortion' cannot "
357 "currently be called with any other option");
358
359 /* Give an ID to recognized coordinate systems. */
360 if(p->wcscoordsys)
361 p->coordsysid=gal_wcs_coordsys_from_string(p->wcscoordsys);
362
363 /* Identify the requested distortion. Note that this also acts as a
364 sanity check because it will crash with an error if the given
365 string isn't recognized. */
366 if(p->wcsdistortion)
367 p->distortionid=gal_wcs_distortion_from_string(p->wcsdistortion);
368
369 /* Make sure the value of '--keyvalue' is actually present. */
370 if(p->keyvalue && p->keyvalue->v[0]=='\0')
371 error(EXIT_FAILURE, 0, "the '--keyvalue' option requires a value: "
372 "the name(s) of keywords you want the value of");
373
374 /* Set the operating mode. */
375 p->mode=FITS_MODE_KEY;
376 }
377
378 /* Same for the extension-related options */
379 if( p->remove || p->copy || p->cut || p->numhdus || p->datasum
380 || p->pixelscale || p->skycoverage || p->hastablehdu
381 || p->hasimagehdu || p->listtablehdus || p->listimagehdus )
382 {
383 /* A small sanity check. */
384 if(p->mode!=FITS_MODE_INVALID)
385 error(EXIT_FAILURE, 0, "extension and keyword manipulation options "
386 "cannot be called together");
387
388 /* Some HDU options cannot be called with other options. */
389 stdoutcheck = ( p->numhdus + p->datasum + p->pixelscale
390 + p->skycoverage + p->hastablehdu + p->hasimagehdu
391 + p->listtablehdus + p->listimagehdus );
392
393 /* Make sure if an output file is needed. */
394 if(stdoutcheck)
395 {
396 /* Make sure HDU reading and editing options aren't called
397 together. */
398 if(p->remove || p->copy || p->cut)
399 error(EXIT_FAILURE, 0, "HDU reading options (like "
400 "'--numhdus', '--datasum' and etc) cannot be called "
401 "with any of the HDU modification options like "
402 "'--remove', '--copy' or '--cut' options");
403
404 /* Make sure these options are called alone. */
405 if(stdoutcheck>1)
406 error(EXIT_FAILURE, 0, "HDU info options, like '--numhdus', "
407 "'--datasum', '--pixelscale' or '--skycoverage', cannot "
408 "be called together, only one at a time");
409
410 /* Make sure the HDU is given if any of the options except
411 '--numhdus' are called. */
412 if( ( p->numhdus || p->hastablehdu || p->hasimagehdu
413 || p->listtablehdus || p->listimagehdus)
414 && p->cp.hdu==NULL )
415 error(EXIT_FAILURE, 0, "a HDU (extension) is necessary for the "
416 "'--datasum', '--pixelscale' or '--skycoverage' options. "
417 "Please use the '--hdu' (or '-h') option to select one");
418 }
419 else
420 {
421 if(p->cp.output)
422 gal_checkset_writable_remove(p->cp.output, 1, p->cp.dontdelete);
423 else
424 p->cp.output=gal_checkset_automatic_output(&p->cp, p->input->v,
425 "_ext.fits");
426 }
427
428 /* Set the operating mode. */
429 p->mode=FITS_MODE_HDU;
430 }
431
432 /* If no options are given, go into HDU mode, which will print the HDU
433 information when nothing is asked. */
434 if(p->mode==FITS_MODE_INVALID)
435 {
436 if(p->hdu_in_commandline)
437 {
438 p->printallkeys=1;
439 p->mode = FITS_MODE_KEY;
440 }
441 else
442 p->mode = FITS_MODE_HDU;
443 }
444 }
445
446
447
448
449
450 static void
ui_check_options_and_arguments(struct fitsparams * p)451 ui_check_options_and_arguments(struct fitsparams *p)
452 {
453 /* Make sure an input file name was given and if it was a FITS file, that
454 a HDU is also given. */
455 if(p->input==NULL)
456 error(EXIT_FAILURE, 0, "no input file is specified");
457 gal_list_str_reverse(&p->input);
458
459 /* More than one input is currently only acceptable with the '--keyvalue'
460 option. */
461 if( gal_list_str_number(p->input) > 1 && p->keyvalue==NULL)
462 error(EXIT_FAILURE, 0, "one input file is expected but %zu input "
463 "files are given", gal_list_str_number(p->input));
464 }
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485 /**************************************************************/
486 /***************** Preparations ********************/
487 /**************************************************************/
488 /* The '--update' and '--write' options take multiple values for each
489 keyword, so here, we tokenize them and put them into a
490 'gal_fits_list_key_t' list. */
491 static void
ui_fill_fits_headerll(gal_list_str_t * input,gal_fits_list_key_t ** output,char * option_name)492 ui_fill_fits_headerll(gal_list_str_t *input, gal_fits_list_key_t **output,
493 char *option_name)
494 {
495 long l, *lp;
496 void *fvalue;
497 double d, *dp;
498 gal_list_str_t *tmp;
499 char *c, *cf, *start, *tailptr;
500 int i=0, type, vfree, needsvalue;
501 char *original, *keyname, *value, *comment, *unit;
502
503 for(tmp=input; tmp!=NULL; tmp=tmp->next)
504 {
505 i=0;
506 tailptr=NULL;
507
508 /* 'c' is created in case of an error, so the input value can be
509 reported. */
510 errno=0;
511 original=malloc(strlen(tmp->v)+1);
512 if(original==NULL)
513 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for 'original'",
514 __func__, strlen(tmp->v)+1);
515 strcpy(original, tmp->v);
516
517 /* Tokenize the input. Note that strlen does not include the \0
518 character. So we have added it with a 1. */
519 cf=(c=start=tmp->v)+strlen(tmp->v)+1;
520 keyname=value=comment=unit=NULL;
521 do
522 {
523 switch(*c)
524 {
525 case ',': case '\0':
526 *c='\0';
527 if(start!=c)
528 switch(i)
529 {
530 case 0:
531 keyname=start;
532 break;
533 case 1:
534 value=start;
535 break;
536 case 2:
537 comment=start;
538 break;
539 case 3:
540 unit=start;
541 break;
542 default:
543 error(EXIT_FAILURE, 0, "%s: only three commas should "
544 "be given in the write or update keyword "
545 "options. The general expected format is:\n"
546 " KEYWORD,value,\"a comment string\",unit\n",
547 original);
548 }
549 ++i;
550 start=c+1;
551 break;
552
553 default:
554 break;
555 }
556 }
557 while(++c<cf);
558
559 /* See if this is an option that needs a value or not.*/
560 needsvalue=1;
561 if(keyname)
562 {
563 if( !strcasecmp(keyname,"checksum")
564 || !strcasecmp(keyname,"datasum") )
565 needsvalue=0;
566 }
567
568 /* Make sure the keyname and value (if necessary) is given. */
569 if( keyname==NULL || (needsvalue && value==NULL) )
570 error(EXIT_FAILURE, 0, "'--%s' option string (%s) can't be parsed. "
571 "The general expected format is (a comment string and unit "
572 "are optional):\n\n"
573 " --%s=KEYWORD,value,\"a comment string\",unit\n\n"
574 "Any space characters around the the comma (,) characters "
575 "will be seen as part of the respective token.\n\n"
576 "Note that there are some exceptions (where no value is need)"
577 "please see the manual for more ('$ info astfits')",
578 option_name, original, option_name);
579 /*
580 printf("\n\n-%s-\n-%s-\n-%s-\n-%s-\n", keyname, value, comment, unit);
581 */
582
583
584 /* Find the type of the value: */
585 if(value)
586 {
587 errno=0;
588 l=strtol(value, &tailptr, 10);
589 if(*tailptr=='\0' && errno==0)
590 {
591 vfree=1;
592 type=GAL_TYPE_INT64;
593 errno=0;
594 fvalue=lp=malloc(sizeof *lp);
595 if(lp==NULL)
596 error(EXIT_FAILURE, errno, "%s: %zu bytes for 'lp'",
597 __func__, sizeof *lp);
598 *lp=l;
599 }
600 else
601 {
602 errno=0;
603 d=strtod(value, &tailptr);
604 if(*tailptr=='\0' && errno==0)
605 {
606 vfree=1;
607 type=GAL_TYPE_FLOAT64;
608 errno=0;
609 fvalue=dp=malloc(sizeof *dp);
610 if(dp==NULL)
611 error(EXIT_FAILURE, errno, "%s: allocating %zu bytes "
612 "for 'dp'", __func__, sizeof *dp);
613 *dp=d;
614 }
615 else
616 { fvalue=value; type=GAL_TYPE_STRING; vfree=0; }
617 }
618 }
619 else
620 {
621 fvalue=NULL; type=GAL_TYPE_UINT8; vfree=0;
622 }
623
624
625 /* Add it to the list of keywords. */
626 gal_fits_key_list_add(output, type, keyname, 0, fvalue, vfree,
627 comment, 0, unit, 0);
628 free(original);
629 }
630
631 /* Reverse the list */
632 gal_fits_key_list_reverse(output);
633 }
634
635
636
637
638
639 static void
ui_preparations(struct fitsparams * p)640 ui_preparations(struct fitsparams *p)
641 {
642 /* Fill in the key linked lists. We want to do this here so if there is
643 any error in parsing the user's input, the error is reported before
644 any change is made in the input file. */
645 if(p->write) ui_fill_fits_headerll(p->write, &p->write_keys, "write");
646 if(p->update) ui_fill_fits_headerll(p->update, &p->update_keys, "update");
647 }
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666 /**************************************************************/
667 /************ Set the parameters *************/
668 /**************************************************************/
669
670 void
ui_read_check_inputs_setup(int argc,char * argv[],struct fitsparams * p)671 ui_read_check_inputs_setup(int argc, char *argv[], struct fitsparams *p)
672 {
673 size_t i;
674 struct gal_options_common_params *cp=&p->cp;
675
676
677 /* Include the parameters necessary for argp from this program ('args.h')
678 and for the common options to all Gnuastro ('commonopts.h'). We want
679 to directly put the pointers to the fields in 'p' and 'cp', so we are
680 simply including the header here to not have to use long macros in
681 those headers which make them hard to read and modify. This also helps
682 in having a clean environment: everything in those headers is only
683 available within the scope of this function. */
684 #include <gnuastro-internal/commonopts.h>
685 #include "args.h"
686
687
688 /* Initialize the options and necessary information. */
689 ui_initialize_options(p, program_options, gal_commonopts_options);
690
691
692 /* Read the command-line options and arguments. */
693 errno=0;
694 if(argp_parse(&thisargp, argc, argv, 0, 0, p))
695 error(EXIT_FAILURE, errno, "parsing arguments");
696
697
698 /* Check if the HDU is specified on the command-line. If so, then later,
699 if no operation is requested, we will print the header of the given
700 HDU.*/
701 for(i=0; !gal_options_is_last(&cp->coptions[i]); ++i)
702 if(cp->coptions[i].key==GAL_OPTIONS_KEY_HDU
703 && cp->coptions[i].set)
704 p->hdu_in_commandline=1;
705
706
707 /* Read the configuration files and set the common values. */
708 gal_options_read_config_set(&p->cp);
709
710
711 /* Read the options into the program's structure, and check them and
712 their relations prior to printing. */
713 ui_read_check_only_options(p);
714
715
716 /* Print the option values if asked. Note that this needs to be done
717 after the option checks so un-sane values are not printed in the
718 output state. */
719 gal_options_print_state(&p->cp);
720
721
722 /* Check that the options and arguments fit well with each other. Note
723 that arguments don't go in a configuration file. So this test should
724 be done after (possibly) printing the option values. */
725 ui_check_options_and_arguments(p);
726
727
728 /* Read/allocate all the necessary starting arrays. */
729 ui_preparations(p);
730 }
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751 /**************************************************************/
752 /************ Free allocated, report *************/
753 /**************************************************************/
754 void
ui_free_and_report(struct fitsparams * p)755 ui_free_and_report(struct fitsparams *p)
756 {
757 /* Free the allocated arrays: */
758 free(p->cp.output);
759 }
760