1# Common funcitons of Bash auto-completion in Gnuastro. For more details on
2# completion, see the "autocomplete feature" section under the "Developing"
3# chapter of Gnuastro's manual and the comments below.
4#
5# This script contains generic functions that can be used by all the
6# programs. Each program also has its own '*-complete.bash' file that will
7# use the generic functions here and define all the internal variables that
8# these functions take as input/output. During the installation, all those
9# '*-complete.bash' files will be appended to this (and installed as one
10# file to be loaded into the user's '.bashrc').
11#
12# Because the combined file is loaded into the user's Bash environment
13# every time, we don't want to complicate the user's environment with
14# global environment variables. As a result, all the functions used in the
15# Bash auto-completion should be 'local' (to that particular function and
16# the functions it calls).
17#
18# To debug/test this script, you can take these steps after building
19# Gnuastro in the program's 'bin/progname/astprogname-complete.bash'
20# file. Building Gnuastro is necessary because one of the files
21#
22#    1. Uncomment the two 'source' lines in the program's completion
23#       script.
24#    2. Correct the un-commented locations. Note that the second is an
25#       automatically built file (it is not under version control or in the
26#       source directory). So in case you have defined a separate build
27#       directory, give that directory. If you are building within your
28#       source (which is generally a bad idea!), it will be the same
29#       directory.
30#    3. Give a value to the 'gnuastro_prefix' variable within
31#       '_gnuastro_autocomplete_astprogname'. This is the location that
32#       Gnuastro is installed in your OS (usually '/usr/local/bin').
33#    4. Activate the program's script with this command in the terminal you
34#       are testing: 'source bin/progname/astprogname-complete.bash' (here
35#       assuming you are in the top Gnuastro source directory, you can run
36#       it from anywhere, just correct the locatation).
37#
38# Original author:
39#     Pedram Ashofteh Ardakani <pedramardakani@pm.me>
40# Contributing author(s):
41#     Mohammad Akhlaghi <mohammad@akhlaghi.org>
42# Copyright (C) 2021 Free Software Foundation, Inc.
43#
44# Gnuastro is free software: you can redistribute it and/or modify it under
45# the terms of the GNU General Public License as published by the Free
46# Software Foundation, either version 3 of the License, or (at your option)
47# any later version.
48#
49# Gnuastro is distributed in the hope that it will be useful, but WITHOUT
50# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
51# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
52# more details.
53#
54# You should have received a copy of the GNU General Public License along
55# with Gnuastro. If not, see <http://www.gnu.org/licenses/>.
56
57
58
59
60
61#######################################################################
62############      Options and general operating mode       ############
63#######################################################################
64
65# Basic initialization.
66_gnuastro_autocomplete_initialize(){
67
68    # Initialize the completion response with null
69    COMPREPLY=();
70
71    # Variable "current", is the current word being completed. "$2" is the
72    # default value for the current word in completion scripts. But we are
73    # using the longer form: "${COMP_WORDS[COMP_CWORD]}" for clarity.
74    current="${COMP_WORDS[COMP_CWORD]}"
75    if [ "$current" = "=" ]; then
76
77        # The equal sign '=' raises complexities when filling suggestions
78        # for long options. Things will work out fine when they are simply
79        # ignored.
80        current=""
81
82    fi
83
84    # Variable "prev", is one word before the one being completed. By
85    # default, this is set as "$3" in completion scripts. But we are using
86    # the longer form: "${COMP_WORDS[COMP_CWORD-1]}" to avoid confusions
87    # with the arguments of our internal functions. Note that Bash will
88    # return the '=' sign as a separate component of the line, so in that
89    # case, we want the second-last word.
90    prev="${COMP_WORDS[COMP_CWORD-1]}"
91    if [ "$prev" = "=" ]; then
92
93        # While a user is writing a long option's argument, the previous
94        # word will be the equal sign '='. This is not helpful at all. But
95        # looking at a word just before '=' helps us understand which long
96        # option is being called upon.
97        prev="${COMP_WORDS[COMP_CWORD-2]}"
98
99    fi
100}
101
102
103
104
105
106# List all the options of the given program (an '=' is kept for the options
107# that need a value). In the output of '--help', option lines have these
108# properties:
109#
110#   - The full line starts with two empty spaces.
111#   - The first non-white character is a '-'.
112#   - It contains a long option formated like '--XXXX'.
113#
114# But some options have a short version (which we ignore in
115# autocompletion), so if the first word ends with a ',' the option name we
116# want to show is the second word.
117_gnuastro_autocomplete_option_list(){
118    options_all=$("${COMP_WORDS[0]}" --help \
119                      | awk '/^  / && $1 ~ /^-/ && /--+[a-zA-Z0-9]*/ { \
120                              if($1 ~ /,$/) name=$2; \
121                              else          name=$1; \
122                              print name}' \
123                      | sed -e's|=.*|=|');
124}
125
126
127
128
129
130# Return successfully if the previous token (specified as first argument)
131# is an option that requires a value and store the option name if it is.
132#
133#   INPUT:
134#     1) [as argument] String to search for.
135#     *) [as variable] 'options_all' (in case the list of options is
136#                      already read)
137_gnuastro_autocomplete_string_is_valued_option(){
138
139    # For easy reading.
140    local string=$1
141
142    # If the first character of the string isn't a '-', then its not an
143    # option and there is no need to do any futher checks (and slow down
144    # the output) so we can safely return failure.
145    if [ ${string:0:1} != "-" ]; then return 1; fi
146
147    # List of option names (with an '=' after those that need a value.
148    if [ x"$options_all" = x ]; then
149        _gnuastro_autocomplete_option_list
150    fi
151
152    # Go over the option list and see if they match the '$1'.
153    for option in $options_all; do
154        if [[ $option = $string=* ]]; then return 0; fi
155    done
156
157    # If control reaches here, then return failure (1).
158    return 1
159}
160
161
162
163
164
165# See if the current word is an argument or option value.
166_gnuastro_autocomplete_mode_arg_optval(){
167
168    # If the previous token is the first token, then this is an
169    # argument, no need for any further checks.
170    if [ $prev = "${COMP_WORDS[0]}" ]; then
171        argument=$current
172
173    # If the previous token is an option that needs a value, then this is
174    # an option value, this function will set 'option_name' and
175    # 'option_name_complete' if necessary.
176    elif _gnuastro_autocomplete_string_is_valued_option $prev; then
177        option_name=$prev
178        option_value=$current
179        option_name_complete=1
180
181    # The previous token wasn't an option that required a value, so this is
182    # an argument.
183    else
184        argument=$current
185    fi
186}
187
188
189
190
191
192# See if this is an argument, option name or option value. This function
193# will fill 'argument', 'option_name' and 'option_value' (they should be
194# initialized to an empty string before it).
195#
196#  option_value=FULL:            We are busy completing an option value.
197#  option_name=FULL:             Can mean different meanings.
198#    {
199#      option_name_complete==0:  We are still filling the option name.
200#      option_name_complete==1:  We are starting the option values.
201#    }
202#  argument=FULL:                We are busy filling an argument.
203#  argument=EMPTY:               We haven't started writing an argument yet.
204_gnuastro_autocomplete_mode(){
205
206    # Local variable necessary only in this function.
207    local namevalue=""
208
209    # If the current word is empty, it may be an argument, or value of an
210    # option (in case a value-required option is given before).
211    if [ x$current = x ]; then
212        _gnuastro_autocomplete_mode_arg_optval
213
214    # The current word isn't empty.
215    else
216
217        # If the current word starts with a '-', it is an option name or
218        # 'name=value' pair.
219        if [ ${current:0:1} = "-" ]; then
220
221            # If there is an equal sign, then we should separate the option
222            # name from the value and keep
223            if [[ $current = *=* ]]; then
224
225                # By setting the "internal field separator" (IFS) to '='
226                # and using 'read', we can separate the strings before and
227                # after the equal sign.
228                IFS="=" read -ra namevalue <<< $current
229                option_name=${namevalue[0]}
230                option_name_complete=1
231
232                # If the value isn't written yet, (for example '--hdu='),
233                # then the second string will just be an '='. But no value
234                # is given yet, so 'option_value' should be empty.
235                option_value=${namevalue[1]}
236                if [ x$option_value = x"\=" ]; then option_value=""; fi
237            else
238                option_name=$current
239                option_name_complete=0
240            fi
241
242        # The current word didn't start with a '-', so it may be an
243        # argument or option value.
244        else
245            # Bash may separate the '=' in 'name=value' tokens. In this
246            # scenario, when the user only gives 'name=' and presses TAB,
247            # then 'current' will be '='. In this case, we should just set
248            # it to empty.
249            if [ $current = "=" ]; then current=""; fi
250
251            # Check to see if its an argument or option value.
252            _gnuastro_autocomplete_mode_arg_optval
253        fi
254
255    fi
256}
257
258
259
260
261# Given a certain option (as first argument), find the value that the user
262# has given for it.
263#
264# OUTPUT:
265#   read_option_value
266_gnuastro_autocomplete_read_option_value(){
267
268    # Inputs:
269    local read_option_name=$1
270
271    # Initialize the output (defined as 'local' before this).
272    read_option_value=""
273
274    # Parse through the given command-line and find the value.
275    local option_found=0
276    for word in ${COMP_WORDS[*]}; do
277
278        # Ignore the program name (first word), current (last) word and any
279        # '=' signs.
280        if [ x$word = x${COMP_WORDS[0]} ] \
281               || [ x$word = x$current ] \
282               || [ x$word = x"=" ]; then
283            local just_a_place_holder=1
284        else
285            # If the 'option_found' flag is set, this is the answer, set it
286            # and return (this has to be *before* the place that we set
287            # 'option_found').
288            if [ $option_found = 1 ]; then
289                read_option_value="$word";
290                return 0
291            fi
292
293            # If this word is the desired option, set the 'option_found'
294            # flag so the next word is taken as the value.
295            if [ x$word = x$read_option_name ]; then option_found=1; fi
296        fi
297    done
298}
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319#######################################################################
320############                     Files                     ############
321#######################################################################
322
323# Check if the given file is a FITS file (that can actually be
324# opened). Note that FITS files have many possible extensions (see the
325# 'gal_fits_name_is_fits' function in 'lib/fits.c').
326_gnuastro_autocomplete_is_fits(){
327    if "$gnuastro_prefix"/astfits "$1" &> /dev/null; then return 0;
328    else                                                  return 1;
329    fi
330}
331
332
333
334
335
336# Return successfully if argument (a FITS file) has a image HDU.
337_gnuastro_autocomplete_fits_has_image(){
338    if _gnuastro_autocomplete_is_fits "$1"; then
339        if [ $("$gnuastro_prefix"/astfits "$1" --hasimagehdu) = 1 ]; then
340            return 0
341        fi
342    fi
343    return 1
344}
345
346
347
348
349
350# Return successfully if argument (a FITS file) has a table HDU.
351_gnuastro_autocomplete_fits_has_table(){
352    if _gnuastro_autocomplete_is_fits "$1"; then
353        if [ $("$gnuastro_prefix"/astfits "$1" --hastablehdu) = 1 ]; then
354            return 0
355        fi
356    fi
357    return 1
358}
359
360
361
362
363
364# Return successfully if first argument is plain-text (not binary).
365_gnuastro_autocomplete_is_plaintext(){
366    if file "$1" | grep 'executable\|binary' &> /dev/null; then return 1;
367    else                                                        return 0;
368    fi
369}
370
371
372
373
374
375# Return successfully (with 0) if the given non-FITS file is a table.
376_gnuastro_autocomplete_is_plaintext_table(){
377
378    # Only do the check if the file exists.
379    if [ -f "$1" ]; then
380
381        # If the file is not plain-text, it will contain an 'executable' or
382        # 'binary' in the output of the 'file' command.
383        if _gnuastro_autocomplete_is_plaintext "$1"; then
384
385            # The file is plain-text. Extract the first non-commented or
386            # empty line and feed it to 'asttable' to see if it can be
387            # interpretted properly. We don't want to bother with the other
388            # lines, because we don't want to waste computational power
389            # here.
390            if awk '!/^#/ && NF>0 {print; exit 0}' "$1" \
391                    | "$gnuastro_prefix"/asttable &> /dev/null; then
392                return 0
393            else
394                return 1
395            fi
396
397            # The file was binary
398        else return 1
399        fi
400
401    # The file didn't exist.
402    else return 1
403    fi
404}
405
406
407
408
409
410# Return successfully if the first argument is a table.
411_gnuastro_autocomplete_is_table(){
412    if   _gnuastro_autocomplete_fits_has_table     "$1"; then return 0
413    elif _gnuastro_autocomplete_is_plaintext_table "$1"; then return 0
414    else                                                      return 1
415    fi
416}
417
418
419
420
421
422# If a certain file (image, table, or certain other files) is already given
423# in the previous tokens, return successfully (with zero), and will put its
424# name in 'given_file'. Otherwise, return a failure (1) and 'given_file'
425# will be untouched.
426_gnuastro_autocomplete_first_in_arguments(){
427
428    # Inputs
429    local mode=$1
430
431    # Local variables (that are only for this function).
432    local word=""
433    local previous=""
434
435    # Initialize outputs
436    given_file=""
437
438    # Go over all the words/tokens given until now.
439    for word in ${COMP_WORDS[*]}; do
440
441        # Ignore the program name (first word), current (last) word, any
442        # directories or '=', or any word that starts with a '-' (which is
443        # an option).
444        if [ x$word = x${COMP_WORDS[0]} ] \
445               || [ x$word = x$current ] \
446               || [ ${word:0:1} = "-" ] \
447               || [ x$word = x"=" ] \
448               || [ -d $word ]; then
449            local just_a_place_holder=1
450        else
451            # If the previous word is a valued option, then it shouldn't be
452            # checked.
453            if _gnuastro_autocomplete_string_is_valued_option $previous; then
454                local just_a_place_holder=1
455
456            # Previous word was not a valued option, do the operation based
457            # on the mode.
458            else
459                case "$mode" in
460                    fits)
461                        if _gnuastro_autocomplete_is_fits "$word"; then
462                            given_file="$word"
463                        fi
464                        ;;
465                    image)
466                        if _gnuastro_autocomplete_fits_has_image "$word"; then
467                            given_file="$word"
468                            return 0;
469                        fi
470                        ;;
471                    table)
472                        if _gnuastro_autocomplete_is_table "$word"; then
473                            given_file="$word"
474                            return 0;
475                        fi
476                        ;;
477                    source_c)
478                        if $(echo "$word" | grep "\.c$" &> /dev/null) \
479                                && [ -f "$word" ]; then
480                            given_file="$word"
481                            return 0;
482                        fi
483                        ;;
484                esac
485            fi
486        fi
487
488        # If this word isn't an '=', put it in 'previous' and go onto the
489        # next word.
490        if [ $word != "=" ]; then
491            previous=$word
492        fi
493    done
494
495    # If control reached here, then there weren't any tables on the
496    # command-line until now.
497    return 1;
498}
499
500
501
502
503
504# Find the requested file from the existing tokens on the command-line.
505#
506# INPUT ARGUMENTS:
507#    1) Mode of file ('table', 'image', 'fits').
508#    2) Name of option containing file names.
509#
510# WRITTEN VARIABLES (should be defined before this function).
511#     given_file: file name of given table.
512_gnuastro_autocomplete_given_file(){
513
514    # Set inputs (for each readability).
515    local mode="$1"
516    local name="$2"
517    local read_option_value=""
518
519    # If 'name' is emtpy, we should look in the arguments, otherwise, we
520    # should look into the options.
521    if [ x"$name" = x ]; then
522        if _gnuastro_autocomplete_first_in_arguments $mode; then
523            # given_file is written by the function as a side-effect.
524            local just_a_place_holder=1
525        fi
526
527    # We are looking for a certain option.
528    else
529        # Read the given option's value.
530        _gnuastro_autocomplete_read_option_value "$name"
531
532        # If we are in image-mode, and the found file has an image, then
533        # put the name in 'given_file' (final output). Same for tables.
534        case $mode in
535            fits)
536                if _gnuastro_autocomplete_is_fits "$read_option_value"; then
537                    given_file="$read_option_value"
538                fi
539                ;;
540            image)
541                if _gnuastro_autocomplete_fits_has_image \
542                       "$read_option_value"; then
543                    given_file="$read_option_value"
544                fi
545                ;;
546            table)
547                if _gnuastro_autocomplete_is_table \
548                       "$read_option_value"; then
549                    given_file="$read_option_value"
550                fi
551                ;;
552        esac
553    fi
554
555}
556
557
558
559
560
561# Find the requested filename and HDU within the already entered
562# command-line. This option needs three arguments:
563#
564# INPUT ARGUMENTS
565#    1) Mode of file ('table' or 'image').
566#    2) The filename option (if empty string, 1st argument).
567#    3) The HDU option (only necessary if the file is FITS).
568#
569# WRITTEN VARIABLES (should be defined before this function).
570#    given_file: file name of given table/image.
571#    given_hdu:  HDU of given table/table.
572_gnuastro_autocomplete_given_file_and_hdu(){
573
574    # Set inputs (for each readability).
575    local mode=$1
576    local name=$2
577    local hduoption=$3
578
579    # Internal variables.
580    local read_option_value=""
581
582    # Initialize the outputs (defined before this).
583    given_hdu=""
584    given_file=""
585
586    # First, confirm the table file name. If requested table is in an
587    # argument, 'name' will be empty.
588    _gnuastro_autocomplete_given_file $mode $name
589
590    # If a file name existed and it is a FITS file, find the HDU given in
591    # the option.
592    if [ x"$given_file" != x ] \
593           && _gnuastro_autocomplete_is_fits "$given_file"; then
594        _gnuastro_autocomplete_read_option_value $hduoption
595        given_hdu="$read_option_value"
596    fi
597}
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618#######################################################################
619############               Completion replies              ############
620#######################################################################
621# Given a set of strings, select the one that matches the first argument
622_gnuastro_autocomplete_compreply_from_string(){
623
624    # Internal variables (for easy reading).
625    local string="$1"
626    local match="$2"
627
628    # When there isn't any match string, just add everything.
629    if [ x"$match" = x ]; then
630        for v in $string; do COMPREPLY+=("$v"); done
631
632    # When there is a match, limit it. We aren't using 'grep' because it
633    # can confuse a possible '--XXX', with its own options on some systems
634    # (and placing a '--' before the search string may not be portable).
635    else
636        for v in $(echo $string \
637                       | awk '{for(i=1;i<=NF;++i) \
638                                 if($i ~ /^'$match'/) print $i}'); do
639            COMPREPLY+=("$v");
640        done
641    fi
642}
643
644
645
646
647# Some options take multiple values, separated by a comma (for example
648# '--option=A,B,C'). Once the possible solutions have been found by the
649# caller function, this function will decide how to print the suggestions:
650# add a comma or not.
651_gnuastro_autocomplete_compreply_comma_when_matched(){
652
653    # Input arguments:
654    local replies="$1"
655    local tomatch="$2"
656    local fullmatch="$3"
657    local continuematch="$4"
658
659    # Add the completion replies.
660    if [ x"$continuematch" = x ]; then
661        for c in $replies; do COMPREPLY+=("$c"); done
662    else
663        # If there is only one match, include the previously specified
664        # replies in the final filled value and append an ',' to let the
665        # user specify more values to the option.
666        if [ $(echo $replies | wc -w) = 1 ]; then
667
668            # In the continue-mode we don't want the final value to be
669            # appended with a space.
670            compopt -o nospace
671
672            # When 'fullmatch' and 'tomatch' are the same, this was the
673            # first requested reply, so we can safely just print it with a
674            # comma.
675            if [ x"$fullmatch" = x"$tomatch" ]; then
676                COMPREPLY+=("$replies,")
677
678            # This was not the first reply, so we need to add the old ones
679            # as a prefix. But we first need to remove any possible start
680            # of the current reply.
681            else
682                local oldreps=$(echo "$fullmatch" | sed -e's|'$tomatch'$||')
683                COMPREPLY+=("$oldreps$replies,")
684            fi
685
686        # There was more than one matching reply, so continue suggesting
687        # with only the column names.
688        else
689            local oldreps=$(echo "$fullmatch" | sed -e's|'$tomatch'$||')
690            for c in $replies; do COMPREPLY+=("$oldreps$c"); done
691        fi
692    fi
693}
694
695
696
697
698
699# Add completion replies for the values to '--searchin'.
700_gnuastro_autocomplete_compreply_searchin(){
701    _gnuastro_autocomplete_compreply_from_string \
702        "name unit comment" "$1"
703}
704
705
706
707
708
709# Add completion replies for the values to '--searchin'.
710_gnuastro_autocomplete_compreply_tableformat(){
711    _gnuastro_autocomplete_compreply_from_string \
712        "fits-ascii fits-binary txt" "$1"
713}
714
715
716
717
718
719# Add completion replies for the values to '--numthreads'.
720_gnuastro_autocomplete_compreply_numthreads(){
721    if nproc &> /dev/null; then
722        local numthreads="$(seq $(nproc))"
723        _gnuastro_autocomplete_compreply_from_string \
724            "$numthreads" "$1"
725    fi
726}
727
728
729
730
731
732# Values to the common '--interpmetric' option.
733_gnuastro_autocomplete_compreply_interpmetric(){
734    _gnuastro_autocomplete_compreply_from_string \
735        "radial manhattan" "$1"
736}
737
738
739
740
741
742# Values to the common '--type' option.
743_gnuastro_autocomplete_compreply_numbertype(){
744    _gnuastro_autocomplete_compreply_from_string \
745        "uint8 int8 uint16 int16 uint32 int32 uint64 int64 float32 float64" \
746        "$1"
747}
748
749
750
751
752
753# Values to the common '--wcslinearmatrix' option.
754_gnuastro_autocomplete_compreply_wcslinearmatrix(){
755    _gnuastro_autocomplete_compreply_from_string \
756        "cd pc" "$1"
757}
758
759
760
761
762
763# Add matching options to the completion replies.
764_gnuastro_autocomplete_compreply_options_all(){
765
766    # Variable only necessary here.
767    local options_match=""
768
769    # Get the list of option names (with an '=' after those that need a
770    # value (if 'options_all' isn't already set)
771    if [ x"$options_all" = x ]; then
772        _gnuastro_autocomplete_option_list
773    fi
774
775    # Limit the options to those that start with the already given portion.
776    if [ x$1 = x ]; then
777        options_match="$options_all"
778    else
779        # We aren't using 'grep' because it can confuse the '--XXX' with
780        # its own options on some systems (and placing a '--' before the
781        # search string may not be portable).
782        options_match=$(echo "$options_all" | awk '/^'$1'/')
783    fi
784
785    # Add the list of options.
786    for f in $options_match; do COMPREPLY+=("$f"); done
787
788    # Disable the extra space on the command-line after the match, only for
789    # this run (only relevant when we have found the match).
790    compopt -o nospace
791}
792
793
794
795
796
797# Add a file into the completion replies. The main point is to remove the
798# fixed directory name prefix of the file (that is appended by 'ls').
799#
800# It takes two arguments:
801#
802#   1. Base string (that was fed into 'ls' to find the full string).
803#      This string CAN BE EMPTY.
804#   2. Full string.
805#
806# If the first is a full directory, then it will remove it from the full
807# string before saving string (which is standard in autocomplete (the user
808# has already given it and it is just annoying!).
809_gnuastro_autocomplete_compreply_file(){
810
811    # For some reason, when there are multiple matches in a sub-directory,
812    # removing the directory removes the whole thing that the user has
813    # already typed! So the part below (which was the main purpose of this
814    # function is currently commented). Until that bug is fixed, we'll just
815    # return the full file name. Here is how you can reproduce the problem
816    # (with the MAIN FUNCTION below uncommented and the WORK AROUND
817    # commented):
818    #
819    #    $ ls
820    #    image.fits
821    #    $ mkdir subdir
822    #    $ mv image.fits subdir/ab-123.fits
823    #    $ cp subdir/ab-123.fits subdir/ab-456.fits
824    #    $ astcrop subdir/ab-[TAB]
825
826    # MAIN FUNCTION
827    #if [ x$1 != x ] && [ -d $1 ]; then COMPREPLY+=("${2#$1}")
828    #else                               COMPREPLY+=("$2")
829    #fi
830
831    # WORK AROUND
832    COMPREPLY+=("$2")
833}
834
835
836
837
838
839# Add all the HDUs that contain a table/image in the first argument (a FITS
840# file) into the completion replies.
841#
842# INPUT ARGUMENTS
843#    1) Mode of file ('table', 'image', or 'all').
844#    2) Name of file.
845#    3) Existing argument.
846_gnuastro_autocomplete_compreply_hdus(){
847
848    # Local variables (for easy reading)
849    local mode="$1"
850    local given_file="$2"
851    local matchstr="$3"
852
853    if _gnuastro_autocomplete_is_fits "$given_file"; then
854
855        # Get list of the file's HDUs.
856        hdus=$("$gnuastro_prefix"/astfits "$given_file" \
857                                 --list"$mode"hdus)
858
859        # Add the matching ones into COMPREPLY.
860        _gnuastro_autocomplete_compreply_from_string \
861            "$hdus" "$matchstr"
862    fi
863}
864
865
866
867
868
869# Add all the keywords in current HDU to the replies.
870#
871# INPUT ARGUMENTS:
872#    1) FITS file name.
873#    2) HDU within the FITS file.
874#    3) Existing argument (to match).
875#    4) If a comma should be appended in case of match.
876_gnuastro_autocomplete_compreply_keys(){
877
878    # Input arguments.
879    local given_file="$1"
880    local given_hdu="$2"
881    local fullmatch="$3"
882    local continuematch="$4"
883
884    # Local variables.
885    local keys=""
886    local tomatch=""
887
888    # Keywords can sometimes be given in series (for example
889    # '--keyvalue=A,B,C'). In this case, the caller will give a non-empty
890    # value to the fourth argument ('continuematch'). We therefore need to
891    # will take the last component of the comma-separated list for the
892    # matching of the next element.
893    if [ x"$continuematch" = x ]; then
894        tomatch="$fullmatch"
895    else
896        tomatch=$(echo $fullmatch | awk 'BEGIN{FS=","} {print $NF}')
897    fi
898
899    # Get list of keywords.
900    keys=$("$gnuastro_prefix"/astfits "$given_file" \
901                             --hdu="$given_hdu" \
902                             --printkeynames \
903               | grep ^$tomatch)
904
905    _gnuastro_autocomplete_compreply_comma_when_matched \
906        "$keys" "$tomatch" "$fullmatch" "$continuematch"
907}
908
909
910
911
912
913# Fill the replies with certain files.
914_gnuastro_autocomplete_compreply_directories(){
915
916    # For easy reading.
917    local arg=$1
918
919    # Get the list of directories (all ending with '/', with '-d' we are
920    # telling 'ls' to not go into sub-directories).
921    local directs=($(ls -d "$arg"*/ 2> /dev/null))
922
923    # If a directory exists at all.
924    if [ x"${directs[0]}" != x ]; then
925
926        # If there is only one match.
927        if [ x"${directs[1]}" = x ]; then
928
929            # If there are sub-directories, then keep the ending '/' and
930            # don't add space (the user may want to add sub-directories).
931            if $(ls -d "${directs[0]}"/*/ &> /dev/null); then
932                COMPREPLY+=("$d")
933                compopt -o nospace
934
935            # There are no sub-directories. In this case, remove the ending
936            # '/' and let Bash add a space after the matched name.
937            else
938                COMPREPLY+=($(echo "${directs[0]}" | sed -e's|/$||'))
939            fi
940
941        # More than one match: go over all the matches and add them. we
942        # should avoid printing a space (so the sub-directories continue in
943        # the same token). Also RECALL THAT '$d' ALREADY ENDS WITH A '/'
944        # HERE.
945        else
946            for d in ${directs[*]}; do
947                COMPREPLY+=("$d")
948                compopt -o nospace
949            done
950        fi
951    fi
952}
953
954
955
956
957
958# Fill the replies with all image formats (note that the 'image' of
959# '_gnuastro_autocomplete_compreply_files_certain' is only FITS files)
960_gnuastro_autocomplete_compreply_images_all(){
961
962    # Local variables to be filled by functions.
963    local arg="$1"
964    local ls_in=""
965    local files=""
966    local suffixes_jpeg=""
967    local suffixes_tiff=""
968
969    # Get the FITS images.
970    _gnuastro_autocomplete_compreply_files_certain image "$arg"
971
972    # If the given name doesn't have a suffix, then search for desired
973    # suffixes.
974    if [ x"$arg" = x"$(echo $arg | cut -d. -f1)" ]; then
975
976        # Since the other formats are checked by suffix and there are many
977        # suffixes, its easier to just call 'ls' once.
978        _gnuastro_autocomplete_compreply_suffixes_jpeg
979        _gnuastro_autocomplete_compreply_suffixes_tiff
980        for s in $suffixes_jpeg $suffixes_tiff; do
981            ls_in="$ls_in "$arg"*$s"
982        done
983
984    # The given argument already contains a suffix. So you can safely
985    # ignore the matched suffixes.
986    else
987        ls_in=$arg"*"
988    fi
989
990    # Find the matching files and add them to the replies.
991    files=($(ls -d $ls_in 2> /dev/null))
992    for f in ${files[*]}; do
993        if [ -d "$f" ]; then
994            COMPREPLY+=("$f/")
995            compopt -o nospace
996        else
997            _gnuastro_autocomplete_compreply_file "$arg" "$f"
998        fi
999    done
1000}
1001
1002
1003
1004
1005# Fill the replies with certain files.
1006_gnuastro_autocomplete_compreply_files_certain(){
1007
1008    # For easy reading.
1009    local arg=$2
1010    local mode=$1
1011
1012    # Get list of matching files (with '-d' we are telling 'ls' to not go
1013    # into sub-directories).
1014    local files=($(ls -d "$arg"* 2> /dev/null))
1015
1016    # Parse the list of files and add it when it is a directory or it can
1017    # be read as a table.
1018    for f in ${files[*]}; do
1019        if [ -d "$f" ]; then
1020            COMPREPLY+=("$f/")
1021            compopt -o nospace
1022        else
1023            case "$mode" in
1024                fits)
1025                    if _gnuastro_autocomplete_is_fits "$f"; then
1026                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
1027                    fi
1028                    ;;
1029                image)
1030                    if _gnuastro_autocomplete_fits_has_image "$f"; then
1031                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
1032                    fi
1033                    ;;
1034                table)
1035                    if _gnuastro_autocomplete_is_table "$f"; then
1036                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
1037                    fi
1038                    ;;
1039                source_c)
1040                    if $(echo $f | grep "\.c$" &> /dev/null); then
1041                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
1042                    fi
1043                    ;;
1044                source_la)
1045                    if $(echo $f | grep "\.la$" &> /dev/null); then
1046                        _gnuastro_autocomplete_compreply_file "$arg" "$f"
1047                    fi
1048                    ;;
1049            esac
1050        fi
1051    done
1052}
1053
1054
1055
1056
1057
1058# Add all table columns in given file (possibly with HDU) that start with
1059# the given string into the completion replies.
1060_gnuastro_autocomplete_compreply_table_columns(){
1061
1062    # Inputs
1063    local table_file="$1"
1064    local table_hdu="$2"
1065    local fullmatch="$3"
1066    local continuematch="$4"
1067
1068    # Internal
1069    local tomatch=""
1070    local columns=""
1071    local hdu_option=""
1072
1073    # If no file has been given, don't set anything, just return.
1074    if [ x"$table_file" = x ]; then return 0; fi
1075
1076    # If a HDU is given, then add it to the options.
1077    if [ x"$table_hdu" != x ]; then hdu_option="--hdu=$table_hdu"; fi
1078
1079    # Columns can usually be given in series (for example
1080    # '--column=A,B,C'). In this case, the caller will give a non-empty
1081    # value to the fourth argument ('continuematch'). We therefore need to
1082    # will take the last component of the comma-separated list for the
1083    # matching of the next element.
1084    if [ x"$continuematch" = x ]; then
1085        tomatch="$fullmatch"
1086    else
1087        tomatch=$(echo $fullmatch | awk 'BEGIN{FS=","} {print $NF}')
1088    fi
1089
1090    # Get the list of columns from the output of '--information': the
1091    # column names are the second column of the lines that start with a
1092    # number. If there is no column name, print the column number.
1093    #
1094    # We are forcing 'awk' to read after the second line of 'asttable'
1095    # output, because the second line contains the filename. The filename
1096    # might start with numbers. If so, there will be an unwanted '(hdu:'
1097    # printed in the results. Here, 'awk' will print the second column in
1098    # lines that start with a number.
1099    columns=$("$gnuastro_prefix"/asttable --information \
1100                                "$table_file" $hdu_option \
1101                  | awk 'NR>2 && /^[0-9]/ { \
1102                            if($2=="n/a") print $1; else print $2 \
1103                         }' \
1104                  | grep ^$tomatch)
1105
1106    # Add the necessary columns in a comma-separated manner.
1107    _gnuastro_autocomplete_compreply_comma_when_matched \
1108        "$columns" "$tomatch" "$fullmatch" "$continuematch"
1109}
1110