-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathargsparse.sh
1981 lines (1828 loc) · 59.9 KB
/
argsparse.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# -*- tab-width: 4; encoding: utf-8; -*-
#
#########
# License:
#
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# Version 2, December 2004
#
# Copyright (C) 2004 Sam Hocevar <[email protected]>
#
# Everyone is permitted to copy and distribute verbatim or modified
# copies of this license document, and changing it is allowed as long
# as the name is changed.
#
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
# 0. You just DO WHAT THE FUCK YOU WANT TO.
#
#########
#
## @file
## @author Damien Nadé <[email protected]>
## @copyright WTFPLv2
## @version 1.8
## @brief Bash Argsparse Library
## @details
## @par URL
## https://github.com/Anvil/bash-argsparse @n
##
## @par Purpose
##
## To replace the option-parsing and usage-describing functions
## commonly rewritten in all scripts.
##
## @note
## This library is implemented for bash version 4. Prior versions of
## bash will fail at interpreting that code.
## @note
## The extglob shell option will be enabled and posix mode will be
## disabled when loading the library. Changing those settings
## afterwards will make the library execution fail.
##
## @par Usage
## Use the argsparse_use_option() function to declare your options with
## their single letter counterparts, along with their description.
##
## @par
## The argsparse_use_option() syntax is:
##
## @code
## argsparse_use_option "optstring" "option description string" \
## [ "property" ... ] [ "optional default value" ]
## @endcode
##
##
## @par
## An "optstring" is of the form "som=estring:". This would declare a
## long option named somestring. The ending ":" is optional and, if
## present, means the long option expects a value on the command
## line. The "=" char is also optional and means the immediatly
## following letter is the short single-letter equivalent option of
## --something.
##
## @par
## The "something" string must only contains ASCII
## letters/numbers/dash/underscore characters.
##
## @note
## What is referred later as "option" or "option name" (or even "long
## option name") is the optstring without the ':' and '=' characters.
##
##
##
## @par Options may have properties.
##
## Properties are set either at option declarations through the
## argsparse_use_option() function, or using the
## argsparse_set_option_property() function
##
## The currently supported properties are:
##
## - "hidden" @n
## An hidden option will not be shown in usage.
##
## - "mandatory" @n
## An option marked as mandatory is required on the command line. If
## a mandatory option is omited by the user, usage() will be
## triggered by argsparse_parse_options().
##
## - "value" @n
## On the command line, the option will require a value.
## Same effect if you end your optstring with a ':' char.
##
## - "default:<defaultvalue>" @n
## The default value for the option.
##
## - "short:<char>" @n
## The short single-letter equivalent of the option.
##
## - "type:<typename>" @n
## Give a type to the option value. User input value will be checked
## against built-in type verifications _or_ the
## "check_type_<typename>" function. You cannot override a built-in
## type. Built-in types are:
## - file
## - directory
## - pipe
## - terminal
## - socket
## - link
## - char
## - unsignedint
## - uint
## - integer
## - int
## - hexa
## - ipv4
## - ipv6
## - ip
## - hostname
## - host
## - portnumber
## - port
## - username
## - group
## - date
## .
##
## - "exclude:<option> <option>" @n
## The exclude property value is a space-separated list of other
## options. User wont be able to provided two mutually exclusive
## options on the command line. @n
##
## e.g: if you set exclude property for the --foo option this way:
## @code argsparse_set_option_property "exclude:opt1 opt2" foo @endcode
## Then --opt1 and --foo are not allowed on the same command line
## invokation. And same goes for --opt2 and --foo.
## This foo exclude property setting wouldnt make --opt1 and --opt2,
## mutually exclusive though.
##
## - "alias:<option> <option>" @n
## This property allows an option to set multiple other without-value
## options instead. Recursive aliases are permitted but no loop
## detection is made, so be careful. @n
## e.g: if you declare an option 'opt' like this:
## @code argsparse_use_option opt "my description" "alias:opt1 opt2" @endcode
## Then if the user is doing --opt on the command line, it will be as
## if he would have done --opt1 --opt2
##
## - cumulative @n
## Implies 'value'.
## Everytime a cumulative option "optionname" is passed on the
## command line, the value is stored at the end of an array named
## "cumulated_values_<optionname>". @n
##
## e.g: for a script with an opt1 option declared this way:
## @code argsparse_use_option opt1 "some description" cumulative @endcode
## and invoked with:
## @code --opt1 value1 --opt1 value2 @endcode
## after argsparse_parse_options(), "${cumulated_values_opt1[0]}" will
## expand to value1, and ${cumulated_values_opt1[1]} will expand to
## value2.
##
## - cumulativeset @n
## Exactly like cumulative, except only uniq values are kept. @n
##
## e.g: for a script with an opt1 option declared this way:
## @code argsparse_use_option opt1 "some description" cumulativeset @endcode
## and invoked with:
## @code --opt1 value1 --opt1 value2 --opt1 value1 @endcode
## after argsparse_parse_options(), "${cumulated_values_opt1[0]}" will
## expand to value1, and "${cumulated_values_opt1[1]}" will expand to
## value2. There would be no "${cumulated_values_opt1[2]}" value.
##
## - "require:<option> <option>" @n
## Creates a dependency between options. if you declare an option with:
## @code
## argsparse_use_option opt1 "something" require:"opt2 opt3"
## @endcode
## argsparse_parse_options() would return with an error if "--opt1"
## is given on the commande line without "--opt2" or without "--opt3".
##
## @par
## You can test if an option has a property using the
## argsparse_has_option_property() function.
## @code argsparse_has_option_property <option> <property> @endcode
##
##
## @par Parsing positionnal parameters
## After the options are declared, invoke the function
## argsparse_parse_options() with the all script parameters. This will
## define:
##
## - program_params, an array, containing all non-option parameters.
##
## - program_options, an associative array. For each record of the
## array:
## - The key is the long option name.
## - And about values:
## - If option doesn't expect a value on the command line, the
## value represents how many times the option has been
## found on the command line
##
## - If option does require a value, the array record value is the
## value of the last occurence of the option found on the command
## line.
## .
## - If option is cumulative (or cumulativeset), the array record
## value is the number of values passed by the user.
## .
##
## After argsparse_parse_options() invokation, you can check if an
## option have was on the command line (or not) using the
## argsparse_is_option_set() function. @n
##
## e.g:
## @code argsparse_is_option_set "long-option-name" @endcode
##
##
## @par The "usage()" function
## If a 'usage' function is defined, and shall
## argsparse_parse_options() return with non-zero status, 'usage' will
## be automatically called.
##
## @note
## This library automatically defines a default 'usage' function,
## which may be removed or overridden by the sourcing program
## afterwards.
##
## @par Value setting internal logic
## During option parsing, for every option of the form '--optionname'
## expecting a value:
##
## - If there exists an array named "option_<optionname>_values" and
## the user-given value doesn't belong to that array, then the
## argsparse_parse_options() function immediately returns with
## non-zero status, triggering 'usage'.
##
## - If the "option_<optionname>_values" array does not exist, but if
## the option has a type property field, then the value format will
## be checked against that type.
##
## - If a function named "check_value_of_<optionname>" has been
## defined, it will be called with the user-given value as its first
## positionnal parameter. If check_value_of_<optionname> returns
## with non-zero status, then parse_option immediately returns with
## non-zero status, triggering the 'usage' function.
##
## @par
## Also, still during option parsing and for @b every option of the form
## "--optionname":
##
## - After value-checking, if a function named
## "set_option_<optionname>" exists, then, instead of directly
## modifying the "program_options" associative array, this function
## is automatically called with 'optionname' as its first
## positionnal parameter, and, if 'optionname' expected a value, the
## value is given as the function second positionnal parameter.
##
## @par About functions return values...
##
## All argsparse functions will return with an error (usually a return
## code of 1) if called with a wrong number of parameters, and return
## with 0 if everything went fine.
##
## @defgroup ArgsparseUsage Calling program usage description message.
## @defgroup ArgsparseOptionSetter Setting options values.
## @defgroup ArgsparseCLIParsing ...
## @defgroup ArgsparseProperty Options properties handling.
## @defgroup ArgsparseParameter Non-optional positionnal parameters.
## @defgroup ArgsparseUtils Misc Functions.
## @defgroup ArgsparseOptionTyping ...
# We're not compatible with older bash versions.
if [[ "$BASH_VERSINFO" -lt 4 ]]
then
printf >&2 "This requires bash >= 4 to run.\n"
return 1 2>/dev/null
exit 1
fi
if ! command -v getopt >/dev/null 2>&1
then
printf >&2 "Cannot find the getopt command.\n"
return 1 2>/dev/null
exit 1
fi
if declare -rp ARGSPARSE_VERSION >/dev/null 2>&1
then
# argsparse is already loaded.
return 0 2>/dev/null
fi
## @var ARGSPARSE_VERSION
## @brief argsparse version number
## @showinitializer
declare -r ARGSPARSE_VERSION=1.8
# Enable required features
shopt -s extglob
set +o posix
## @brief The name of the program currently using argsparse.
## @details Automatically set by argsparse at load time, it contains
## the basename (path-less but with extension, if any) of the main
## script. Used internally to print error messages and by
## usage-related functions, but can be used by the loading script.
## @hideinitializer
declare -r argsparse_pgm=${0##*/}
## @var String __argsparse_environment_variable_prefix
## @brief The prefix for option-setting environment variables
## @private
## @ingroup ArgsparseOptionSetter
declare __argsparse_environment_variable_prefix
## @fn argsparse_use_environment_variables()
## @brief Enable option setting through environment variables.
## @details
## Once enabled, it cannot be disabled.
## @param prefix an environment variable prefix
## @ingroup ArgsparseOptionSetter
argsparse_use_environment_variables() {
local prefix
case $# in
0)
prefix=$(argsparse_option_to_identifier "${argsparse_pgm%.sh}")
prefix=${prefix^^}
;;
1)
prefix=$1
;;
*)
return 1
esac
__argsparse_environment_variable_prefix=$prefix
}
## @fn argsparse_are_environment_variables_enabled()
## @brief Tell if option setting through environment variables is
## enabled or not.
## @details
## @retval 0 if argsparse_parse_options would also retrieve option
## values from environment variables.
## @ingroup @ArgsparseOptionSetter
argsparse_are_environment_variables_enabled() {
[[ ${__argsparse_environment_variable_prefix:-} ]]
}
## @fn argsparse_option_environment_variable_name()
## @brief Give the environment variable name linked to an option
## @details0
## Return the name of the environment variable name that can be used
## to set a given option. Works whether option setting through
## environment variables is enabled or not.
## @param option an option name
## @return the environment variable name linked to the option
argsparse_option_environment_variable_name() {
[[ $# -eq 1 ]] || return 1
local option=$1
local identifier=$(argsparse_option_to_identifier "$option")
printf %s_%s "$__argsparse_environment_variable_prefix" "${identifier^^}"
}
## @fn argsparse_option_environment_variable()
## @brief Give the content of the environment variable that can be
## used to set a given option.
## @details
## Does not check if option setting through environment variable is
## enabled or not.
## @param option An option name
## @return The content of the environment variable that matches the
## given option.
## @retval 0 if the variable that matches the given option actually
## exists (even if its value is empty)
argsparse_option_environment_variable() {
[[ $# -eq 1 ]] || return 1
local option=$1
local var=$(argsparse_option_environment_variable_name "$option")
if [[ -v $var ]]; then
printf %s "${!var}"
return 0
fi
return 1
}
## @fn argsparse_set_option_from_environment_value()
## @brief Give a value to an option, from environment variable value.
## @details
## Options can be set from environment variables, and in some cases,
## those values have special meanings:
## - 0, "false", "no" and the empty string are explicit values to
## disable a non-valued option
## - (probably more to come)
## .
## @param option an option name
## @param value the value, as found in the process environment, to set
## the option to.
argsparse_set_option_from_environment_value() {
[[ $# -eq 2 ]] || return 1
local option=$1
local value=$2
if ! argsparse_has_option_property "$option" value
then
if [[ $value != +(0|false|no|"") ]]
then
argsparse_set_option "$option"
fi
# There should be an elif statement here, if we want to consider
# cumulated options differently.
else
argsparse_set_option "$option" "$value"
fi
}
## @fn __argsparse_parse_options_env_and_defaults()
## @brief set options from environment and from default values.
## @details
## For every option that have not been set from command line, look for
## a matching environment variable (when
## argsparse_are_environment_variables_enabled() is true) and apply
## default values right after if still left unset.
## @private
__argsparse_parse_options_env_and_defaults() {
local option value
for option in "${!__argsparse_options_descriptions[@]}"
do
if ! argsparse_is_option_set "$option"
then
if argsparse_are_environment_variables_enabled &&
value=$(argsparse_option_environment_variable "$option")
then
argsparse_set_option_from_environment_value "$option" "$value"
elif __argsparse_has_array_item \
__argsparse_options_default_values "$option"
then
argsparse_set_option "$option" \
"${__argsparse_options_default_values[$option]}"
fi
fi
done
}
## @fn __argsparse_index_of()
## @param value a value
## @param values... array values
## @brief Tells if a value is found in a set of other values.
## @details Look for @a value and print its position in the @a values
## set. Return false if it can not be found.
## @retval 0 if @a value is amongst @a values
## @retval 1 if @a value is not found.
## @ingroup ArgsparseUtils
__argsparse_index_of() {
[[ $# -ge 2 ]] || return 1
local key=$1 ; shift
local index=0
local elem
for elem in "$@"
do
if [[ "$key" = "$elem" ]]
then
printf %s "$index"
return 0
fi
: $((index++))
done
return 1
}
## @fn __argsparse_join_array()
## @param c a single char
## @param strings... strings to join
## @brief join multiple strings by a char.
## @details Like the 'str.join' string method in python, join multiple
## strings by a char. Only work with a single char, though.
## @retval 1 if first parameter is invalid.
## @retval 0 else.
## @ingroup ArgsparseUtils
__argsparse_join_array() {
[[ $# -ge 1 && $1 = ? ]] || return 1
local IFS="$1$IFS"
shift
printf %s "$*"
}
# @private
# @fn __argsparse_max_length()
# @details Prints the length of the longest argument _or_ 50.
# @brief Internal use.
# @param string... a list of strings
# @return 0
__argsparse_max_length() {
local max=50
shift
local max_length=0 str
for str in "$@"
do
max_length=$((max_length>${#str}?max_length:${#str}))
done
printf %d "$((max_length>max?max:max_length))"
}
## @fn __argsparse_is_array_declared()
## @param name A potential array name
## @retval 0 if an array named after the parameter does exist
## @ingroup ArgsparseUtils
__argsparse_is_array_declared() {
[[ $# -eq 1 ]] || return 1
local array_name=$1
[[ "$(declare -p "$array_name" 2>/dev/null)" = \
"declare -"[aA]" $array_name="* ]]
}
## @fn __argsparse_has_array_item()
## @param array_name an array name.
## @param item an item key.
## @retval 0 if an array has been already declared by the name of
## the parameter AND if said array holds given key.
## @ingroup ArgsparseUtils
__argsparse_has_array_item() {
[[ $# = [12] ]] || return 1
local array_name=$1
local index=${2:-@}
local var="$array_name[$index]"
(
set +o nounset
[[ ${!var+set} = set ]]
)
}
## @fn argsparse_option_to_identifier()
## @brief Give the identifier name associated to an option.
## @details Transforms and prints an option name into a string which
## suitable to be part of a function or a variable name.
## @param option an option name.
## @ingroup ArgsparseUtils
argsparse_option_to_identifier() {
[[ $# -eq 1 ]] || return 1
local option=$1
printf %s "${option//-/_}"
}
## @fn __argsparse_values_array_identifier()
## @private
## @brief Prints the name of the array containing all user-declared
## acceptable values for an option.
## @details from "opt" or "opt-name" string, prints
## "option_opt_values[@]" or "option_opt_name_values[@]", unless array
## is not declared, in which case function will return an error.
## @param option an option name.
## @retval 1 if array has not been declared
## @retval 0 else. Array name will be written to stdout.
## @ingroup ArgsparseUtils
__argsparse_values_array_identifier() {
local option=$1
local array="option_$(argsparse_option_to_identifier "$option")_values"
__argsparse_is_array_declared "$array" || return 1
printf %s'[@]' "$array"
}
## @brief Internal use only.
## @details An associative array where options default values are
## stored as soon as the 'default:' property is set.
## @ingroup ArgsparseProperty
declare -A __argsparse_options_default_values=()
## @var AssociativeArray __argsparse_options_properties
## @private
## @brief Internal use only.
## @ingroup ArgsparseProperty
declare -A __argsparse_options_properties=()
# Association short option -> long option.
## @var AssociativeArray __argsparse_short_options
## @private
## @brief Internal use only.
declare -A __argsparse_short_options=()
# @fn __argsparse_optstring_has_short()
# @brief Internal use only.
# @details Prints the short option string suitable for getopt command
# line.
# @param optstring an optstring
# @return non-zero if given optstring doesnt have any short option
# equivalent.
__argsparse_optstring_has_short() {
[[ $# -eq 1 ]] || return 1
local optstring=$1
if [[ "$optstring" =~ .*=(.).* ]]
then
printf %c "${BASH_REMATCH[1]}"
return 0
fi
return 1
}
## @fn argsparse_set_option_property()
## @brief Enable a property to a list of options.
## @param property a property name.
## @param option... option names.
## @return non-zero if property is not supported.
## @ingroup ArgsparseProperty
argsparse_set_option_property() {
[[ $# -ge 2 ]] || return 1
local property=$1
shift
local option p short
for option in "$@"
do
case "$property" in
cumulative|cumulativeset)
argsparse_set_option_property value "$option"
;;&
type:*|exclude:*|alias:*|require:*)
if [[ "$property" =~ ^.*:(.+)$ ]]
then
# If property has a value, check its format, we
# dont want any funny chars.
if [[ "${BASH_REMATCH[1]}" = *[*?!,]* ]]
then
printf >&2 "%s: %s: invalid property value.\n" \
"$argsparse_pgm" "${BASH_REMATCH[1]}"
return 1
fi
fi
;&
mandatory|hidden|value|cumulative|cumulativeset)
# We use the comma as the property character separator
# in the __argsparse_options_properties array.
p=${__argsparse_options_properties["$option"]:-}
__argsparse_options_properties["$option"]="${p:+$p,}$property"
;;
short:?)
short=${property#short:}
if __argsparse_has_array_item \
__argsparse_short_options "$short"
then
printf >&2 \
"%s: %s: short option for %s conflicts with already-configured short option for %s.\n" \
"$argsparse_pgm" "$short" "$option" \
"${__argsparse_short_options[$short]}"
return 1
fi
__argsparse_short_options["$short"]=$option
;;
default:*)
# The default value
__argsparse_options_default_values["$option"]=${property#default:}
;;
*)
return 1
;;
esac
done
}
## @fn argsparse_has_option_property()
## @brief Determine if an option has a property.
## @details Return True if property has been set for given option, and
## print the property value, if available.
## @param option an option name.
## @param property a property name.
## @retval 0 if option has given property.
## @ingroup ArgsparseProperty
argsparse_has_option_property() {
[[ $# -eq 2 ]] || return 1
local option=$1
local property=$2
local p=${__argsparse_options_properties["$option"]:-""}
if [[ "$p" =~ (^|.+,)"$property"(:([^,]+))?($|,.+) ]]
then
printf %s "${BASH_REMATCH[3]}"
elif [[ $property = default && \
"${__argsparse_options_default_values[$option]+yes}" = yes ]]
then
print %s "${__argsparse_options_default_values[$option]}"
else
return 1
fi
}
## @fn argsparse_short_to_long()
## @brief
## @param char a short option letter
## @return if found, the long option name matching given short option
## letter.
argsparse_short_to_long() {
[[ $# -eq 1 ]] || return 1
local char=$1
local long=${__argsparse_short_options[$char]}
[[ -n "$long" ]] && printf %s "$long"
}
# This is an associative array. It should contains records of the form
# "something" -> "Some usage description string".
# The "something" string is referred as the "option name" later in
# source code and comments.
## @var AssociativeArray __argsparse_options_descriptions
## @private
## @brief Internal use only.
declare -A __argsparse_options_descriptions=()
# @fn __argsparse_check_declaration_conflict()
# @brief Internal use.
# @details Check if an option conflicts with another and, if it does,
# prints the conflicted option.
# @param option an option name
# @return True if option *does* conflict with a previously declared
# option.
__argsparse_check_declaration_conflict() {
[[ $# -eq 1 ]] || return 1
local option=$1
local identifier=$(argsparse_option_to_identifier "$option")
local -a identifiers=("${!__argsparse_tmp_identifiers[@]}")
local conflict
if __argsparse_has_array_item identifiers @ && \
conflict=$(__argsparse_index_of "$identifier" "${identifiers[@]}")
then
printf %s "${__argsparse_tmp_identifiers[${identifiers[$conflict]}]}"
return 0
fi
__argsparse_tmp_identifiers["$identifier"]=$option
return 1
}
## @fn argsparse_use_option()
## @brief Define a @b new option.
## @param optstring an optstring.
## @param description the option description, for the usage() function.
## @param property... an non-ordered list of keywords. Recognized
## property keywords are:
## - mandatory: missing option will trigger usage. If a default
## value is given, the option is considered as if provided on
## the command line.
## - hidden: option wont show in default usage function.
## - value: option expects a following value.
## - short:c: option has a single-lettered (c) equivalent.
## - exclude:"option1 [ option2 ... ]" option is not
## compatible with other options option1, option2...
## - cumulative
## - cumulativeset
## - type:sometype
## - The @b last non-keyword parameter will be considered as the
## default value for the option. All other parameters and
## values will be ignored. - might be broken / obsolete and broken
## @retval 0 if no error is encountered.
## @retval 2 if option name is bad (a message will be printed)
## @retval 3 if option name conflicts with another option (a message
## will be printed)
## @retval 4 if a wrong property name is provided. (a message will be
## printed)
argsparse_use_option() {
[[ $# -ge 2 ]] || return 1
local optstring=$1
local description=$2
shift 2
local long short conflict
# configure short option.
if short=$(__argsparse_optstring_has_short "$optstring")
then
set -- "short:$short" "$@"
optstring=${optstring/=/}
fi
# --$optstring expect an argument.
if [[ "$optstring" = *: ]]
then
set -- value "$@"
long=${optstring%:}
else
long=$optstring
fi
if [[ "$long" = *[!-0-9a-zA-Z_]* ]]
then
printf >&2 "%s: %s: bad option name.\n" "$argsparse_pgm" "$long"
return 2
fi
if conflict=$(__argsparse_check_declaration_conflict "$long")
then
printf >&2 "%s: %s: option conflicts with already-declared %s.\n" \
"$argsparse_pgm" "$long" "$conflict"
return 3
fi
__argsparse_options_descriptions["$long"]=$description
# Any other parameter to this function should be a property.
while [[ $# -ne 0 ]]
do
if ! argsparse_set_option_property "$1" "$long"
then
printf >&2 '%s: %s: unknown property.\n' "$argsparse_pgm" "$1"
return 4
fi
shift
done
}
## @fn argsparse_option_description()
## @brief Prints to stdout the description of given option.
## @param option an option name.
## @retval 0 if given option has been previously declared.
argsparse_option_description() {
[[ $# -eq 1 ]] || return 1
local option=$1
[[ -n "${__argsparse_options_descriptions[$option]+yes}" ]] && \
printf %s "${__argsparse_options_descriptions[$option]}"
}
## @fn argsparse_check_option_type()
## @brief Check if a value matches a given type.
## @details Return True if @a value is of type @a type.
## @param type A case-insensitive type name.
## @param value a value to check.
## @retval 0 if the value matches the given type format.
## @ingroup ArgsparseOptionTyping
argsparse_check_option_type() {
[[ $# -eq 2 ]] || return 1
local option_type=${1,,}
local value=$2
local t
case "$option_type" in
file|directory|pipe|terminal)
# [[ wont accept the -$var as an operator.
[ -"${option_type:0:1}" "$value" ]
;;
socket|link)
t=${option_type:0:1}
[ -"${t^^}" "$value" ]
;;
char)
[[ "$value" = ? ]]
;;
unsignedint|uint)
[[ "$value" = +([0-9]) ]]
;;
integer|int)
[[ "$value" = ?(-)+([0-9]) ]]
;;
hexa)
[[ "$value" = ?(0x)+([a-fA-F0-9]) ]]
;;
ipv4)
# Regular expression for ipv4 and ipv6 have been found on
# http://www.d-sites.com/2008/10/09/regex-ipv4-et-ipv6/
[[ "$value" =~ ^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ ]]
;;
ipv6)
[[ "$value" =~ ^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(([0-9A-Fa-f]{1,4}:){0,5}:((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(::([0-9A-Fa-f]{1,4}:){0,5}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$ ]]
;;
ip)
# Generic IP address.
argsparse_check_option_type ipv4 "$value" || \
argsparse_check_option_type ipv6 "$value"
;;
hostname)
# check if value resolv as an IPv4 or IPv6 address.
host -t a "$value" >/dev/null 2>&1 || \
host -t aaaa "$value" >/dev/null 2>&1
;;
host)
# An hostname or an IP address.
argsparse_check_option_type hostname "$value" || \
argsparse_check_option_type ipv4 "$value" || \
argsparse_check_option_type ipv6 "$value"
;;
portnumber)
argsparse_check_option_type uint "$value" && \
[[ "$value" -gt 0 && "$value" -le 65536 ]]
;;
port)
# Port number or service.
argsparse_check_option_type portnumber "$value" || \
getent services "$value" >/dev/null 2>&1
;;
username)
getent passwd "$value" >/dev/null 2>&1
;;
group)
getent group "$value" >/dev/null 2>&1
;;
date)
date --date "$value" >/dev/null 2>&1
;;
*)
# Invoke user-defined type-checking function if available.
if ! declare -f "check_option_type_$option_type" >/dev/null
then
printf >&2 \
"%s: %s: type has no validation function. This is a bug.\n" \
"$argsparse_pgm" "$option_type"
return 2
fi
"check_option_type_$option_type" "$value"
;;
esac
}
# The usage-related functions.
## @var Array __argsparse_parameters_description
## @private
## @brief Internal use only.
## @ingroup ArgsparseUsage
# @details Used by argsparse_describe_parameters() to store
# non-option positionnal parameters short descriptions and by
# argsparse_usage_short() to print them to end-users.
declare -a __argsparse_parameters_description
__argsparse_usage_short_line_management() {
[[ $# -eq 1 ]] || return 1
local next_token=$1
local max_length=78
local bigger_line
bigger_line="$current_line $next_token"
if [[ "${#bigger_line}" -gt "$max_length" ]]
then
printf -- '%s \\\n' "$current_line"
printf -v current_line "\t%s" "$next_token"
else
current_line=$bigger_line
fi
}
## @fn argsparse_usage_short()
## @brief Print a short description of the program syntax.
## @details Generate and print the "short" description of the program
## usage.
## @ingroup ArgsparseUsage
argsparse_usage_short() {
local option values current_line current_option param
local max_length=78
current_line=$argsparse_pgm
for option in "${!__argsparse_options_descriptions[@]}"
do
if argsparse_has_option_property "$option" hidden
then
continue
fi
current_option="--$option"
if argsparse_has_option_property "$option" value
then
if values=$(__argsparse_values_array_identifier "$option")
then
current_option="$current_option <$(
__argsparse_join_array '|' "${!values}")>"
else
current_option="$current_option ${option^^}"
fi
fi
if ! argsparse_has_option_property "$option" mandatory
then
current_option="[ $current_option ]"
fi
__argsparse_usage_short_line_management "$current_option"
done
if __argsparse_has_array_item __argsparse_parameters_description @
then
for param in "${__argsparse_parameters_description[@]}"
do
__argsparse_usage_short_line_management "$param"
done
fi
printf -- "%s\n" "$current_line"
}
## @fn argsparse_describe_parameters()
## @brief Describe non-option positionnal parameters.