1 /*********************************************************************
2 Query - Retreive data from a remote data server.
3 Query 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) 2020-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/fits.h>
32 #include <gnuastro/pointer.h>
33 
34 #include <gnuastro-internal/timing.h>
35 #include <gnuastro-internal/options.h>
36 #include <gnuastro-internal/checkset.h>
37 #include <gnuastro-internal/fixedstringmacros.h>
38 
39 #include "main.h"
40 
41 #include "ui.h"
42 #include "query.h"
43 #include "authors-cite.h"
44 
45 
46 
47 
48 
49 /**************************************************************/
50 /*********      Argp necessary global entities     ************/
51 /**************************************************************/
52 /* Definition parameters for the Argp: */
53 const char *
54 argp_program_version = PROGRAM_STRING "\n"
55                        GAL_STRINGS_COPYRIGHT
56                        "\n\nWritten/developed by "PROGRAM_AUTHORS;
57 
58 const char *
59 argp_program_bug_address = PACKAGE_BUGREPORT;
60 
61 static char
62 args_doc[] = "DATABASE";
63 
64 const char
65 doc[] = GAL_STRINGS_TOP_HELP_INFO PROGRAM_NAME" is just a place holder "
66   "used as a minimal set of files and functions necessary for a program in "
67   "Gnuastro. It can be used for learning or as a template to build new "
68   "programs.\n"
69   GAL_STRINGS_MORE_HELP_INFO
70   /* After the list of options: */
71   "\v"
72   PACKAGE_NAME" home page: "PACKAGE_URL;
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 /**************************************************************/
94 /*********    Initialize & Parse command-line    **************/
95 /**************************************************************/
96 static void
ui_initialize_options(struct queryparams * p,struct argp_option * program_options,struct argp_option * gal_commonopts_options)97 ui_initialize_options(struct queryparams *p,
98                       struct argp_option *program_options,
99                       struct argp_option *gal_commonopts_options)
100 {
101   size_t i;
102   struct gal_options_common_params *cp=&p->cp;
103 
104 
105   /* Set the necessary common parameters structure. */
106   cp->program_struct     = p;
107   cp->poptions           = program_options;
108   cp->program_name       = PROGRAM_NAME;
109   cp->program_exec       = PROGRAM_EXEC;
110   cp->program_bibtex     = PROGRAM_BIBTEX;
111   cp->program_authors    = PROGRAM_AUTHORS;
112   cp->coptions           = gal_commonopts_options;
113 
114   /* Program-specific initializations. */
115   p->head                = GAL_BLANK_SIZE_T;
116 
117   /* Modify common options. */
118   for(i=0; !gal_options_is_last(&cp->coptions[i]); ++i)
119     {
120       /* Select individually. */
121       switch(cp->coptions[i].key)
122         {
123         case GAL_OPTIONS_KEY_LOG:
124         case GAL_OPTIONS_KEY_TYPE:
125         case GAL_OPTIONS_KEY_SEARCHIN:
126         case GAL_OPTIONS_KEY_QUIETMMAP:
127         case GAL_OPTIONS_KEY_IGNORECASE:
128         case GAL_OPTIONS_KEY_NUMTHREADS:
129         case GAL_OPTIONS_KEY_MINMAPSIZE:
130         case GAL_OPTIONS_KEY_STDINTIMEOUT:
131           cp->coptions[i].flags=OPTION_HIDDEN;
132           break;
133         }
134 
135       /* Select by group. */
136       switch(cp->coptions[i].group)
137         {
138         case GAL_OPTIONS_GROUP_TESSELLATION:
139           cp->coptions[i].doc=NULL; /* Necessary to remove title. */
140           cp->coptions[i].flags=OPTION_HIDDEN;
141           break;
142         }
143     }
144 }
145 
146 
147 
148 
149 
150 /* Fixed string */
151 #define UI_NODATABASE "Please use the '--database' ('-d') option to "   \
152   "specify your desired database, see manual ('info gnuastro "          \
153   "astquery' command) for the current databases, here is the list "     \
154   "of acceptable values (with their web-based search URLs):\n\n"        \
155   "    astron     https://vo.astron.nl/\n"                              \
156   "    gaia       https://gea.esac.esa.int/archive\n"                   \
157   "    ned        https://ned.ipac.caltech.edu/tap/sync\n"              \
158   "    vizier     http://vizier.u-strasbg.fr/viz-bin/VizieR\n"
159 
160 
161 
162 
163 /* Parse a single option: */
164 error_t
parse_opt(int key,char * arg,struct argp_state * state)165 parse_opt(int key, char *arg, struct argp_state *state)
166 {
167   struct queryparams *p = state->input;
168 
169   /* Pass 'gal_options_common_params' into the child parser.  */
170   state->child_inputs[0] = &p->cp;
171 
172   /* In case the user incorrectly uses the equal sign (for example
173      with a short format or with space in the long format, then 'arg'
174      start with (if the short version was called) or be (if the long
175      version was called with a space) the equal sign. So, here we
176      check if the first character of arg is the equal sign, then the
177      user is warned and the program is stopped: */
178   if(arg && arg[0]=='=')
179     argp_error(state, "incorrect use of the equal sign ('='). For short "
180                "options, '=' should not be used and for long options, "
181                "there should be no space between the option, equal sign "
182                "and value");
183 
184   /* Set the key to this option. */
185   switch(key)
186     {
187     /* Read the non-option tokens (arguments): */
188     case ARGP_KEY_ARG:
189       /* The user may give a shell variable that is empty! In that case
190          'arg' will be an empty string! We don't want to account for such
191          cases (and give a clear error that no input has been given). */
192       if(arg[0]!='\0') p->databasestr=arg;
193       break;
194 
195     /* This is an option, set its value. */
196     default:
197       return gal_options_set_from_key(key, arg, p->cp.poptions, &p->cp);
198     }
199 
200   return 0;
201 }
202 
203 
204 
205 
206 
207 char *
ui_strlist_to_str(gal_list_str_t * input)208 ui_strlist_to_str(gal_list_str_t *input)
209 {
210   char *out=NULL;
211   gal_list_str_t *node;
212   size_t n, nn, nnodes=0, alllen=0;
213 
214   /* First calculate the full length of all nodes. */
215   for(node=input; node!=NULL; node=node->next)
216     {
217       /* We'll add two extra for each. One for the ',' that must come in
218          between it and the next one. One just for a buffer, incase we
219          haven't accounted for something. */
220       alllen += strlen(node->v) + 2;
221       ++nnodes;
222     }
223 
224   /* Allocate the output string. */
225   out=gal_pointer_allocate(GAL_TYPE_STRING, alllen, 1, "out", __func__);
226 
227   /* Write all the strings into the allocated space. */
228   n=nn=0;
229   for(node=input; node!=NULL; node=node->next)
230     {
231       if(nn++==nnodes-1)
232         sprintf(out+n, "%s", node->v);
233       else
234         n += sprintf(out+n, "%s,", node->v);
235     }
236 
237   /* Return the merged string. */
238   return out;
239 }
240 
241 
242 
243 
244 
245 
246 
247 
248 
249 
250 
251 
252 
253 
254 
255 
256 
257 
258 
259 
260 /**************************************************************/
261 /***************       Sanity Check         *******************/
262 /**************************************************************/
263 /* Read and check ONLY the options. When arguments are involved, do the
264    check in 'ui_check_options_and_arguments'. */
265 static void
ui_read_check_only_options(struct queryparams * p)266 ui_read_check_only_options(struct queryparams *p)
267 {
268   size_t i;
269   double *darray;
270   gal_data_t *tmp;
271   int keepinputdir;
272   char *suffix, *rdsuffix, *basename;
273 
274   /* See if database has been specified. */
275   if(p->databasestr==NULL)
276     error(EXIT_FAILURE, 0, "no input database! " UI_NODATABASE);
277 
278   /* Convert the given string into a code. */
279   if(      !strcmp(p->databasestr, "astron") ) p->database=QUERY_DATABASE_ASTRON;
280   else if( !strcmp(p->databasestr, "gaia") )   p->database=QUERY_DATABASE_GAIA;
281   else if( !strcmp(p->databasestr, "ned") )    p->database=QUERY_DATABASE_NED;
282   else if( !strcmp(p->databasestr, "vizier") ) p->database=QUERY_DATABASE_VIZIER;
283   else
284     error(EXIT_FAILURE, 0, "'%s' is not a recognized database.\n\n"
285           "For the full list of recognized databases, please see the "
286           "documentation (with the command 'info astquery')", p->databasestr);
287 
288   /* If '--limitinfo' is given, but the string is empty (possibly due to a
289      shell variable that wasn't set), remove it. */
290   if(p->limitinfo && p->limitinfo[0]=='\0')
291     {
292       free(p->limitinfo);
293       p->limitinfo=NULL;
294       for(i=0; !gal_options_is_last(&p->cp.poptions[i]); ++i)
295         if( p->cp.poptions[i].key == UI_KEY_LIMITINFO )
296           p->cp.poptions[i].set=GAL_OPTIONS_NOT_SET;
297     }
298 
299   /* If '--ccol' is given, first merge all possible calls to it, confirm
300      that there are only two values and put them into the 'ra_name' and
301      'dec_name' variables. */
302   if(p->ccol)
303     {
304       gal_options_merge_list_of_csv(&p->ccol);
305       if(gal_list_str_number(p->ccol)!=2)
306         error(EXIT_FAILURE, 0, "2 values should be given to '--ccol', "
307               "but you have given %zu values (possibly in multiple calls "
308               "to '--ccols'). Recall that '--ccol' is the coordinate "
309               "column (usually RA and Dec). You can either put them in "
310               "one call (for example '--ccol=ra,dec') or in two (for "
311               "example '--ccol=ra --ccol=dec')",
312               gal_list_str_number(p->ccol));
313       p->ra_name=p->ccol->v;
314       p->dec_name=p->ccol->next->v;
315     }
316 
317   /* If '--noblank' or '--sort' are given (possibly multiple times, each
318      with multiple column names) break it up into individual names. */
319   if(p->sort)    gal_options_merge_list_of_csv(&p->sort);
320   if(p->noblank) gal_options_merge_list_of_csv(&p->noblank);
321 
322   /* Make sure that '--query' and '--center' are not called together. */
323   if(p->query && (p->center || p->overlapwith) )
324     error(EXIT_FAILURE, 0, "the '--query' option cannot be called together "
325           "together with '--center' or '--overlapwith'");
326 
327   /* Overlapwith cannot be called with the manual query. */
328   if( p->overlapwith && (p->center || p->width || p->radius) )
329     error(EXIT_FAILURE, 0, "the '--overlapwith' option cannot be called "
330           "with the manual region specifiers ('--center', '--width' or "
331           "'--radius')");
332 
333   /* The radius and width cannot be called together. */
334   if(p->radius && p->width)
335     error(EXIT_FAILURE, 0, "the '--radius' and '--width' options cannot be "
336           "called together");
337 
338   /* If radius is given, it should be one value and positive. */
339   if(p->radius)
340     {
341       if(p->radius->size>1)
342         error(EXIT_FAILURE, 0, "only one value can be given to '--radius' "
343               "('-r') option");
344 
345       if( ((double *)(p->radius->array))[0]<0 )
346         error(EXIT_FAILURE, 0, "the '--radius' option value cannot be negative");
347     }
348 
349   /* Make sure the range values are reasonable. */
350   i=0;
351   if(p->range)
352     for(tmp=p->range; tmp!=NULL; tmp=tmp->next)
353       {
354         /* Basic preparations. */
355         ++i;
356         darray=tmp->array;
357 
358         /* Make sure only two values are given. */
359         if(tmp->size!=2)
360           error(EXIT_FAILURE, 0, "two values (separated by ',' or ':') "
361                 "should be given to '--range'. But %zu values were given "
362                 "to the %zu%s call of this option (recall that the first "
363                 "value should be the column name in the given dataset)",
364                 tmp->size, i,
365                 i==1 ? "st" : i==2 ? "nd" : i==3 ? "rd" : "th");
366 
367         /* Make sure the first value is large than the second. */
368         if(darray[0]>darray[1])
369           error(EXIT_FAILURE, 0, "the first value of '--range' "
370                 "should be smaller than, or equal to, the second, "
371                 "but %g>%g", darray[0], darray[1]);
372 
373         /* None of the values should be 'nan'. */
374         if( isnan(darray[0]) || isnan(darray[1]) )
375           error(EXIT_FAILURE, 0, "values to '--range' cannot be NaN");
376 
377         /* ADQL doesn't recognize 'inf', so if the user gives '-inf' or
378            'inf', change it to the smallest/largest possible floating point
379            number. */
380         if( isinf(darray[0]) == -1 ) darray[0] = -FLT_MAX;
381         if( isinf(darray[1]) ==  1 ) darray[1] =  FLT_MAX;
382       }
383 
384   /* Make sure the widths are reasonable. */
385   if(p->width && p->center)
386     {
387       /* Width should have the same number of elements as the center
388          coordinates */
389       if( p->width->size > 1 && p->width->size != p->center->size )
390         error(EXIT_FAILURE, 0, "'--width' should either have a single "
391               "value (used for all dimensions), or one value for each "
392               "dimension. However, you have provided %zu coordinate "
393               "values, and %zu width values", p->center->size,
394               p->width->size);
395 
396       /* All values must be positive. */
397       for(i=0;i<p->width->size;++i)
398         if( ((double *)(p->width->array))[i]<0 )
399           error(EXIT_FAILURE, 0, "the '--width' option value(s) cannot "
400                 "be negative");
401     }
402 
403   /* Make sure that the output name is in a writable location and that it
404      doesn't exist. If it exists, and the user hasn't called
405      '--dontdelete', then delete the existing file. */
406   gal_checkset_writable_remove(p->cp.output, p->cp.keep,
407                                p->cp.dontdelete);
408 
409   /* Set the suffix of the default download names for NED (since extinction
410      is given only in VOTable, with an '.xml' suffix). */
411   if( p->database==QUERY_DATABASE_NED
412       && p->datasetstr
413       && !strcmp(p->datasetstr, "extinction") )
414     {
415       suffix=".xml";
416       rdsuffix="-raw-download.xml";
417     }
418   else
419     {
420       suffix=".fits";
421       rdsuffix="-raw-download.fits";
422     }
423 
424   /* Currently Gnuastro doesn't read or write XML files (VOTable). So if
425      the downloaded file is an XML file but the user hasn't given an XML
426      suffix, abort and inform the user. */
427   if(p->cp.output)
428     {
429       if( !strcmp(suffix,".xml")
430           && strcmp(&p->cp.output[strlen(p->cp.output)-4], ".xml") )
431         error(EXIT_FAILURE, 0, "this dataset's output is a VOTable (with "
432               "an '.xml' suffix). However, Gnuastro doesn't yet support "
433               "VOTable, so it won't do any checks and corrections on "
434               "the downloaded file. Please give an output name with an "
435               "'.xml' suffix to continue");
436     }
437 
438   /* Set the name for the downloaded and final output name. These are due
439      to an internal low-level processing that will be done on the raw
440      downloaded file. */
441   else
442     {
443       basename=gal_checkset_malloc_cat(p->databasestr, suffix);
444       p->cp.output=gal_checkset_make_unique_suffix(basename, suffix);
445       free(basename);
446     }
447 
448   /* Currently we don't interally process VOTable (in '.xml' suffix) files,
449      so to keep the next steps un-affected, we'll set Query to not delete
450      the raw download and copy the name of the output into the raw
451      download. */
452   if( !strcmp(suffix, ".xml") )
453     {
454       p->keeprawdownload=1;
455       gal_checkset_allocate_copy(p->cp.output, &p->downloadname);
456     }
457   else
458     {
459       /* Make sure the output name doesn't exist (and report an error if
460          '--dontdelete' is called. Just note that for the automatic output,
461          we are basing that on the output, not the input. So we are
462          temporarily activating 'keepinputdir'. */
463       keepinputdir=p->cp.keepinputdir;
464       p->cp.keepinputdir=1;
465       gal_checkset_writable_remove(p->cp.output, 0, p->cp.dontdelete);
466       p->downloadname=gal_checkset_automatic_output(&p->cp, p->cp.output,
467                                                     rdsuffix);
468       p->cp.keepinputdir=keepinputdir;
469     }
470 }
471 
472 
473 
474 
475 
476 static void
ui_check_options_and_arguments(struct queryparams * p)477 ui_check_options_and_arguments(struct queryparams *p)
478 {
479 
480 }
481 
482 
483 
484 
485 
486 
487 
488 
489 
490 
491 
492 
493 
494 
495 
496 
497 
498 
499 
500 
501 /**************************************************************/
502 /***************       Preparations         *******************/
503 /**************************************************************/
504 static void
ui_preparations(struct queryparams * p)505 ui_preparations(struct queryparams *p)
506 {
507 
508 }
509 
510 
511 
512 
513 
514 
515 
516 
517 
518 
519 
520 
521 
522 
523 
524 
525 
526 
527 
528 /**************************************************************/
529 /************         Set the parameters          *************/
530 /**************************************************************/
531 void
ui_read_check_inputs_setup(int argc,char * argv[],struct queryparams * p)532 ui_read_check_inputs_setup(int argc, char *argv[], struct queryparams *p)
533 {
534   struct gal_options_common_params *cp=&p->cp;
535 
536 
537   /* Just to avoid warning on no minmapsize. It is irrelevant here. */
538   p->cp.minmapsize=-1;
539 
540 
541   /* Include the parameters necessary for argp from this program ('args.h')
542      and for the common options to all Gnuastro ('commonopts.h'). We want
543      to directly put the pointers to the fields in 'p' and 'cp', so we are
544      simply including the header here to not have to use long macros in
545      those headers which make them hard to read and modify. This also helps
546      in having a clean environment: everything in those headers is only
547      available within the scope of this function. */
548 #include <gnuastro-internal/commonopts.h>
549 #include "args.h"
550 
551 
552   /* Initialize the options and necessary information.  */
553   ui_initialize_options(p, program_options, gal_commonopts_options);
554 
555 
556   /* Read the command-line options and arguments. */
557   errno=0;
558   if(argp_parse(&thisargp, argc, argv, 0, 0, p))
559     error(EXIT_FAILURE, errno, "parsing arguments");
560 
561 
562   /* Read the configuration files and set the common values. */
563   gal_options_read_config_set(&p->cp);
564 
565 
566   /* Read the options into the program's structure, and check them and
567      their relations prior to printing. */
568   ui_read_check_only_options(p);
569 
570 
571   /* Print the option values if asked. Note that this needs to be done
572      after the option checks so un-sane values are not printed in the
573      output state. */
574   gal_options_print_state(&p->cp);
575 
576 
577   /* Prepare all the options as FITS keywords to write in output later. */
578   gal_options_as_fits_keywords(&p->cp);
579 
580 
581   /* Check that the options and arguments fit well with each other. Note
582      that arguments don't go in a configuration file. So this test should
583      be done after (possibly) printing the option values. */
584   ui_check_options_and_arguments(p);
585 
586 
587   /* Read/allocate all the necessary starting arrays. */
588   ui_preparations(p);
589 }
590 
591 
592 
593 
594 
595 
596 
597 
598 
599 
600 
601 
602 
603 
604 
605 
606 
607 
608 
609 
610 /**************************************************************/
611 /************      Free allocated, report         *************/
612 /**************************************************************/
613 void
ui_free_report(struct queryparams * p,struct timeval * t1)614 ui_free_report(struct queryparams *p, struct timeval *t1)
615 {
616   /* Free the allocated arrays: */
617   free(p->cp.hdu);
618   free(p->cp.output);
619   free(p->downloadname);
620 }
621