Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added bash completion support #6

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 75 additions & 20 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,44 +1,99 @@
#Optparse
A BASH wrapper for getopts, for simple command-line argument parsing
A BASH wrapper for getopts and compgen, for simple command-line argument parsing an bash completion.

##What is this?
A wrapper that provides a clean and easy way to parse arguments to your BASH scripts. It lets you define short and long option names, handle flag variables, and set default values for optional arguments, all while aiming to be as minimal as possible: *One line per argument definition*.
A wrapper that provides a clean and easy way to parse arguments and create Tab completion to your BASH scripts. You could also create wrappers for linux commands. It let you define short and long option names, handle flag variables, set default values for optional arguments and an optional list of posible values for options to complete, all while aiming to be as minimal as possible: *One line per argument definition*.

##Usage
##### See `sample_head.sh` for a demonstration of optparse
###1. Define your arguments
###1. Install bash-completion package from your linux repository.
###2. Create a script to generate completion and option parsing files.
###3. Source file optparse.bash in the script.
###4. Define your arguments in the script.

Each argument to the script is defined with `optparse.define`, which specifies the option names, a short description, the variable it sets and the default value (if any).
Each argument to the script is defined with `optparse.define`, which specifies the option names, a short description, the variable it sets, the default value (if any) and a list of posible values (if any). Also you could define options as required to your script. If you use filesystem file parameters you could define the parameter as a file.

```bash
optparse.define short=f long=file desc="The input file" variable=filename
optparse.define short=n long=name desc="The event name" variable=name
```

Flags are defined in exactly the same way, but with an extra parameter `value` that is assigned to the variable.

```bash
optparse.define short=v long=verbose desc="Set flag for verbose mode" variable=verbose_mode value=true default=false
```
optparse.define short=s long=say-hello desc="Say Hello" variable=say_hello value=true default=false
```

Posible arguments could be defined in exactly the same way, but with an extra parameter `list` that is assigned to the variable.

```bash
optparse.define short=c long=country desc="The event country" variable=country list="USA Canada Japan Brasil England"
```

Required parameters are defined in exactly the same way, but with an extra parameter `required` that is assigned to the variable.

```bash
optparse.define short=y long=year desc="The event year" variable=year required=true
```

Another way to pass a completion list is to assign a command output to list variable as follow.

```bash
optparse.define short=y long=year desc="The event year" variable=year list="\$(my_command)"
```

File parameters are defined in exactly the same way, but with an extra parameter `file` that is assigned to the variable. See `wget_dir_generate_completion.sh` as example.

```bash
optparse.define short=x long=directory-prefix desc="Destination directory to save all files" variable=directory file=true required=true
```

###5. Evaluate your arguments
The `optparse.build` function creates a header script and a configuration file in /etc/bash_completion.d/ based on the provided argument definitions.

```bash
optparse.build script_name
```
If you want to generate completion file in another location, you could path location as parameter to `optparse.build` function.

###2. Evaluate your arguments
The `optparse.build` function creates a temporary header script based on the provided argument definitions. Simply source the file the function returns, to parse the arguments.
```bash
optparse.build script_name "."
```

###6. Allow execution to the script and execute it as sudo.
##### See `sample_event_generate_completion.sh` for a demonstration. For a more complex example see `wget_dir_generate_completion.sh`.
###7. Source profile bash completion configuration, example:
```bash
source $( optparse.build )
$ source /etc/bash_completion
```

###8. In your command script( The script to parse arguments and generate completion ) source the optparse generated file to parse and evaluate arguments. Also you could check if there are missing options.

####That's it!
The script can now make use of the variables. Running the script (without any arguments) should give you a neat usage description.
The script can now show completion and make use of the variables. Running the script (without any arguments) should give you a neat usage description.

usage: ./script.sh [OPTIONS]

OPTIONS:

-f --file : The input file
-v --verbose : Set flag for verbose mode
-n --name : The event name
-s --say-hello: Say Hello [default:false]
-c --country : The country name
-y --year : The year

-? --help : usage


##### See `sample_event.sh` for a demonstration. For a more complex example see `wget_dir.sh`.
### Now to use the command script we can write:
```bash
$ ./sample_event.sh [TAB][TAB]
$ --name --country --year
$ ./sample_event.sh --country [TAB][TAB]
$ USA Canada Japan Brasil
$ ./sample_event.sh --name "Football World Cup" --country Brasil --year 2014
$ The Football World Cup will be in Brasil in 2014.
$ ./sample_event.sh --sey-hello --name "Football World Cup" --country Brasil --year 2014
$ Hello!!! The Football World Cup will be in Brasil in 2014.
```

##Supported definition parameters
All definition parameters for `optparse.define` are provided as `key=value` pairs, seperated by an `=` sign.
####`short`
Expand All @@ -53,13 +108,13 @@ the value to set the variable to. If unspecified, user is expected to provide a
a short description of the argument (to build the usage description)
####`default`(optional)
the default value to set the variable to if argument not specified
####`list`(optional)
the list of posible arguments for an option to autocomplete. It could be a list of strings or a command.
####`required`(optional)
the requirement for the option

##Installation
1. Download/clone `optparse.bash`
2. Add

```bash
`source /path/to/optparse.bash`
```
to `~/.bashrc`


105 changes: 96 additions & 9 deletions optparse.bash
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
# Optparse - a BASH wrapper for getopts
#!/bin/bash
# Optparse - a BASH wrapper for getopts and compgen
# @author : nk412 / [email protected]

optparse_usage=""
optparse_contractions=""
optparse_defaults=""
optparse_process=""
optparse_arguments_string=""
optparse_process_completion=""
short_options=""
long_options=""
required_short_options=""
required_long_options=""
declare -A hash_options

# -----------------------------------------------------------------------------------------------------------------------------
function optparse.throw_error(){
Expand Down Expand Up @@ -45,6 +52,12 @@ function optparse.define(){
local variable="$value"
elif [ "$key" = "value" ]; then
local val="$value"
elif [ "$key" = "list" ]; then
local list="$value"
elif [ "$key" = "file" ]; then
local file="$value"
elif [ "$key" = "required" ]; then
local required="$value"
fi
done

Expand All @@ -57,25 +70,53 @@ function optparse.define(){
fi

# build OPTIONS and help
optparse_usage="${optparse_usage}#NL#TB${short} $(printf "%-25s %s" "${long}:" "${desc}")"
optparse_usage="${optparse_usage}#NL#TB${short} $(printf "%-25s %s" "${long}:" "${desc}")"
if [ "$default" != "" ]; then
optparse_usage="${optparse_usage} [default:$default]"
fi
optparse_contractions="${optparse_contractions}#NL#TB#TB${long})#NL#TB#TB#TBparams=\"\$params ${short}\";;"
optparse_contractions="${optparse_contractions}#NL#TB#TB${long}=*)#NL#TB#TB#TBparams=\"\$params ${short}=\${param#*=}\";;"
if [ "$default" != "" ]; then
optparse_defaults="${optparse_defaults}#NL${variable}=${default}"
fi
optparse_arguments_string="${optparse_arguments_string}${shortname}"
if [ "$val" = "\$OPTARG" ]; then
optparse_arguments_string="${optparse_arguments_string}:"
fi
optparse_process="${optparse_process}#NL#TB#TB${shortname})#NL#TB#TB#TB${variable}=\"$val\";;"
optparse_process="${optparse_process}#NL#TB#TB${shortname})#NL#TB#TB#TB${variable}=\"$val\""
optparse_process="${optparse_process}#NL#TB#TB#TB\$(grep -q '^=' <<< \"\$OPTARG\") && hash_options[-${shortname}\"\$OPTARG\"]=${long}\"\$OPTARG\";;"
# Complete options
long_options="${long_options} ${long}"
short_options="${short_options} ${short}"
# Complete command arguments
if [ "$list" != "" ]; then
optparse_process_completion="${optparse_process_completion}#NL#TB#TB${long})#NL#TB#TB#TB${variable}_list=\"$list\"#NL#TB#TB#TBCOMPREPLY=( \$(compgen -W \"\${${variable}_list}\" -- \${cur}) )#NL#TB#TB#TBreturn 0;;"
fi
if [ "$file" == "true" ]; then
optparse_process_completion="${optparse_process_completion}#NL#TB#TB${long})#NL#TB#TB#TBcompopt -o default#NL#TB#TB#TBCOMPREPLY=()#NL#TB#TB#TBreturn 0;;"
fi
# Take obligatory parameters
if [ "$required" == "true" ]; then
required_short_options="${required_short_options} ${short}"
required_long_options="${required_long_options} ${long}"
fi
hash_options["${short}"]="${long}"
}

# -----------------------------------------------------------------------------------------------------------------------------
function optparse.build(){
local build_file="/tmp/optparse-${RANDOM}.tmp"

local script=$1
local completion_dir=$2
if [[ -z "$script" ]]; then
build_file="/tmp/optparse-${RANDOM}.tmp"
else
build_file="${script}_optparse"
if [[ -z "$completion_dir" ]]; then
completion_dir="/etc/bash_completion.d"
fi
completion_file="${completion_dir}/${script}_completion"
fi

# Building getopts header here

# Function usage
Expand Down Expand Up @@ -118,29 +159,69 @@ eval set -- "\$params"
# Set default variable values
$optparse_defaults

# Process using getopts
# Get required options and parameters
required_short_options="$( sed 's/^ //' <<< "$required_short_options")"
required_long_options="$( sed 's/^ //' <<< "$required_long_options")"

# Create an associative array with with short options as keys and long options as values
declare -A hash_options=(\
$(for option in ${short_options}
do
echo -n "[$option]=${hash_options[$option]} "
done))

# Process using getopts
while getopts "$optparse_arguments_string" option; do
# Return error when argument is an option of type Ex: --option or --option=xxxxx
[[ -n "\$OPTARG" ]] && [[ "\${required_short_options}" == *"\${OPTARG/=*/}"* ]] && echo "Invalid parameter: \${hash_options["\${OPTARG/=*/}"]}"=\${OPTARG#*=} && usage
case \$option in
# Substitute actions for different variables
$optparse_process
:)
echo "Option - \$OPTARG requires an argument"
exit 1;;
*)
*)
echo "Unknown option: \$option"
usage
exit 1;;
esac
done

# Clean up after self
rm $build_file
[[ -z "$script" ]] && rm $build_file

EOF

# Create completion script
if [[ -n "$completion_file" ]]; then
cat << EOF > $completion_file
_$script(){
local cur prev options
compopt +o default
COMPREPLY=()
cur=\${COMP_WORDS[COMP_CWORD]}
prev=\${COMP_WORDS[COMP_CWORD-1]}

# The basic options we'll complete.
options="${long_options}"

# Complete the arguments to some of the basic commands.
case \$prev in
$optparse_process_completion
*)
esac
COMPREPLY=(\$(compgen -W "\${options}" -- \${cur}))
return 0
}
complete -F _$script $script
EOF
fi

local -A o=( ['#NL']='\n' ['#TB']='\t' )

for i in "${!o[@]}"; do
sed -i "s/${i}/${o[$i]}/g" $build_file
[[ -n "$completion_file" ]] && sed -i "s/${i}/${o[$i]}/g" $completion_file
done

# Unset global variables
Expand All @@ -149,8 +230,14 @@ EOF
unset optparse_arguments_string
unset optparse_defaults
unset optparse_contractions
unset long_options
unset optparse_process_completion
unset short_options
unset required_short_options
unset required_long_options
unset hash_options

# Return file name to parent
echo "$build_file"
[[ -z "$script" ]] && echo "$build_file" || true
}
# -----------------------------------------------------------------------------------------------------------------------------
28 changes: 28 additions & 0 deletions sample_event.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash

# Source the sample_event_optparse file ---------------------------------------------------
source sample_event.sh_optparse

# Take options enter by user
tr ' ' '\n' <<< "$@" | grep "\-" | sed -e 's/=.*//g' | sort > /tmp/options_entered

# Compare options defined as required with options enter by user to obtain missing options
missing_options=$( tr ' ' '\n' <<< "$required_short_options" | sort | comm -32 - /tmp/options_entered | tr '\n' ' ' )

if [ -n "$missing_options" ]; then
echo "Missing required option: $missing_options"
usage;
exit 1;
else

# Display event information
if [[ "$say_hello" == "true" ]]; then
salute="Hello!!! "
fi
echo $salute "The $name event will be in $country in $year."
exit 0
fi




16 changes: 16 additions & 0 deletions sample_event_generate_completion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

# Source the optparse.bash file ---------------------------------------------------
source optparse.bash

# Define options
optparse.define short=n long=name desc="The event name" variable=name
optparse.define short=s long=say-hello desc="Say Hello" variable=say_hello value=true default=false
optparse.define short=c long=country desc="The event country" variable=country list="USA Canada Japan Brasil England"
optparse.define short=y long=year desc="The event year" variable=year required=true

# Generate optparse and autocompletion scripts
script_name="sample_event.sh"
optparse.build $script_name

exit 0
35 changes: 35 additions & 0 deletions wget_dir.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash

# Source the sample_event_optparse file ---------------------------------------------------
source wget_dir.sh_optparse

# Command
cmd="wget"

# Take options enter by user
tr ' ' '\n' <<< "$@" | grep "\-" | sed -e 's/=.*//g' | sort > /tmp/options_entered

# Compare options defined as required with options enter by user to obtain missing options
missing_options=$( tr ' ' '\n' <<< "$required_short_options" | sort | comm -32 - /tmp/options_entered | tr '\n' ' ' )

if [ -n "$missing_options" ]; then
echo "Missing required option: $missing_options"
usage;
exit 1;
else
# Add options and parameters to command
for option in "$@"
do
if [ -z "${hash_options[$option]}" ]; then
cmd="$cmd $option"
else
cmd="$cmd ${hash_options[$option]}"
fi
done
# Execute command
echo "Executing command: "
echo "$cmd"
eval "$cmd"
exit 0
fi

Loading