diff --git a/package/bin/bin.mk b/package/bin/bin.mk index 5ac94ac9..15d276a9 100644 --- a/package/bin/bin.mk +++ b/package/bin/bin.mk @@ -11,7 +11,7 @@ BIN_LICENSE = BSD-3-Clause BIN_LICENSE_FILES = LICENSE BIN_REDISTRIBUTE = NO BIN_DEPENDENCIES = sysrepo libite -BIN_CONF_OPTS = --prefix= --disable-silent-rules +BIN_CONF_OPTS = --disable-silent-rules BIN_AUTORECONF = YES define BIN_CONF_ENV diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index 1345414c..f33dd3e9 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -3,6 +3,10 @@ ACLOCAL_AMFLAGS = -I m4 bin_PROGRAMS = copy erase files +# Bash completion +bashcompdir = $(datadir)/bash-completion/completions +dist_bashcomp_DATA = copy.bash + copy_SOURCES = copy.c util.c util.h copy_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE copy_CFLAGS = -W -Wall -Wextra diff --git a/src/bin/copy.bash b/src/bin/copy.bash new file mode 100644 index 00000000..f46b54be --- /dev/null +++ b/src/bin/copy.bash @@ -0,0 +1,93 @@ +# bash completion for copy command +# SPDX-License-Identifier: ISC + +_copy_completion() +{ + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + # Options for the copy command + opts="-h -n -q -s -t -u -v" + + local datastores_dst="running-config startup-config" + local datastores_src="running-config startup-config factory-config" + + case "${prev}" in + -t) + # Timeout expects a number, no completion + return 0 + ;; + -u) + # Username, could complete with getent passwd but keep it simple + return 0 + ;; + esac + + # If current word starts with -, complete options + if [[ ${cur} == -* ]]; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + # Determine position (source or destination) + # Count non-option arguments before current position + local arg_count=0 + local i + for ((i=1; i < COMP_CWORD; i++)); do + case "${COMP_WORDS[i]}" in + -h|-n|-q|-s|-v) + # Flag without argument + ;; + -t|-u) + # Flag with argument, skip next word + ((i++)) + ;; + -*) + # Unknown flag, might have argument + ;; + *) + # Non-option argument + ((arg_count++)) + ;; + esac + done + + # Helper function to add non-hidden files/dirs, filtering out dotfiles unless explicitly requested + _add_files() { + local IFS=$'\n' + local files + + # If user is explicitly typing a dotfile path, include dotfiles + if [[ ${cur} == .* || ${cur} == */.* ]]; then + files=( $(compgen -f -- "${cur}") ) + else + # Only show non-hidden files and directories + files=( $(compgen -f -- "${cur}" | grep -v '/\.[^/]*$' | grep -v '^\.[^/]') ) + fi + + COMPREPLY+=( "${files[@]}" ) + } + + case ${arg_count} in + 0) + # First argument (source): complete with files and all datastores including factory-config + COMPREPLY=( $(compgen -W "${datastores_src}" -- ${cur}) ) + _add_files + ;; + 1) + # Second argument (destination): complete with files and limited datastores (no factory-config) + COMPREPLY=( $(compgen -W "${datastores_dst}" -- ${cur}) ) + _add_files + ;; + *) + # No more arguments expected + return 0 + ;; + esac + + return 0 +} + +complete -F _copy_completion copy diff --git a/src/bin/copy.c b/src/bin/copy.c index 6f667d8d..bc3d14cd 100644 --- a/src/bin/copy.c +++ b/src/bin/copy.c @@ -503,13 +503,23 @@ static int usage(int rc) printf("Usage: %s [OPTIONS] SRC DST\n" "\n" "Options:\n" - " -h This help text\n" - " -n Dry-run, validate configuration without applying\n" - " -q Quiet mode, suppress informational messages\n" - " -s Sanitize paths for CLI use (restrict path traversal)\n" - " -u USER Username for remote commands, like scp\n" - " -t SEEC Timeout for the operation, or default %d sec\n" - " -v Show version\n", prognm, timeout); + " -h This help text\n" + " -n Dry-run, validate configuration without applying\n" + " -q Quiet mode, suppress informational messages\n" + " -s Sanitize paths for CLI use (restrict path traversal)\n" + " -t SEC Timeout for the operation, or default %d sec\n" + " -u USER Username for remote commands, like scp\n" + " -v Show version\n" + "\n" + "Files:\n" + " SRC JSON configuration file, or a datastore\n" + " DST A file or datastore, except factory-config\n" + "\n" + "Datastores:\n" + " running-config The running datastore, current active config\n" + " startup-config The non-volatile config used at startup\n" + " factory-config The device's factory default configuration\n" + "\n", prognm, timeout); return rc; }