diff --git a/README.md b/README.md index 10dd653..6d87ca4 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ sudo mandb wcurl ... wcurl [--curl-options ]... [--no-decode-filename] [-o|-O|--output ] [--dry-run] [--] ... wcurl [--curl-options=]... [--no-decode-filename] [--output=] [--dry-run] [--] ... +wcurl [--save-call=:]... [--list-save] [--rm-save=] +wcurl [-r|--run-save=] ... wcurl -V|--version wcurl -h|--help ``` @@ -95,6 +97,22 @@ should be using curl directly if your use case is not covered. Do not actually execute curl, just print what would be invoked. +* `--save-call=:` + + Save a curl call to `$HOME/.wcurlrc` for reuse. Name must contain only alphanumeric characters, dashes, and underscores. Supports parameter expansion using `!1`, `!2`, `!3` as placeholders. + +* `-r, --run-save=` + + Run a saved curl call from `$HOME/.wcurlrc`. For calls with parameter expansion, provide parameters as separate arguments after the name. + +* `--list-save` + + List all saved curl calls. + +* `--rm-save=` + + Remove a saved curl call. + * `-V, --version` Print version information. @@ -146,6 +164,25 @@ then performs the parsing. May be specified more than once. wcurl --curl-options="--parallel-max-host 0" example.com/filename1.txt example.com/filename2.txt ``` +* Save curl options for reuse: + + ```sh + wcurl --save-call=quiet:"--silent --show-error" + ``` + +* Use a saved call: + + ```sh + wcurl --run-save=quiet example.com/file.txt + ``` + +* Save with parameter expansion: + + ```sh + wcurl --save-call=getFile:'-o !1 !2' + wcurl -r=getFile output.txt example.com/file.txt + ``` + # Running the testsuite If you would like to run the tests, you first need to install the diff --git a/tests/tests.sh b/tests/tests.sh index ccfc23b..1d9b58f 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -233,6 +233,262 @@ testUrlDecodingNonLatinLanguages() assertContains "Verify whether 'wcurl' successfully decodes percent-encoded Korean in URLs" "${ret}" '퍼센트_인코딩' } +# Tests for --save-call, --run-save, --list-save, --rm-save +testSaveCallBasic() +{ + # Create a temporary .wcurlrc for testing + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + + # Clean up any existing test file + rm -f "${TEST_WCURLRC}" + + # Test saving a simple call + wcurl --save-call=testcall:"--silent --show-error" >/dev/null 2>&1 + assertTrue "Verify whether '--save-call' exits successfully" "$?" + + # Verify the file was created + assertTrue "Verify whether .wcurlrc file was created" "[ -f ${TEST_WCURLRC} ]" + + # Verify the content + ret=$(cat "${TEST_WCURLRC}") + assertContains "Verify whether '--save-call' saves correctly" "${ret}" "testcall=--silent --show-error" + + # Clean up + rm -f "${TEST_WCURLRC}" +} + +testSaveCallMultiple() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save multiple calls + wcurl --save-call=call1:"--silent" --save-call=call2:"--verbose" >/dev/null 2>&1 + assertTrue "Verify whether multiple '--save-call' options work" "$?" + + ret=$(cat "${TEST_WCURLRC}") + assertContains "Verify whether first call is saved" "${ret}" "call1=--silent" + assertContains "Verify whether second call is saved" "${ret}" "call2=--verbose" + + rm -f "${TEST_WCURLRC}" +} + +testSaveCallWithParameterExpansion() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save a call with parameter expansion + wcurl --save-call=paramtest:'-o !1 !2' >/dev/null 2>&1 + assertTrue "Verify whether '--save-call' with parameter expansion exits successfully" "$?" + + ret=$(cat "${TEST_WCURLRC}") + assertContains "Verify whether parameter expansion is saved correctly" "${ret}" "paramtest=-o !1 !2" + + rm -f "${TEST_WCURLRC}" +} + +testSaveCallUpdate() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save initial call + wcurl --save-call=testcall:"--silent" >/dev/null 2>&1 + + # Update the same call + wcurl --save-call=testcall:"--verbose" >/dev/null 2>&1 + assertTrue "Verify whether '--save-call' can update existing calls" "$?" + + ret=$(cat "${TEST_WCURLRC}") + assertContains "Verify whether updated call has new value" "${ret}" "testcall=--verbose" + assertNotContains "Verify whether old value is removed" "${ret}" "testcall=--silent" + + # Verify only one entry exists + count=$(grep -c "testcall=" "${TEST_WCURLRC}") + assertEquals "Verify whether only one entry exists after update" "1" "${count}" + + rm -f "${TEST_WCURLRC}" +} + +testSaveCallMissingColon() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Try to save without colon separator + ret=$(wcurl --save-call=testcall 2>&1) + assertFalse "Verify whether '--save-call' without colon fails" "$?" + assertContains "Verify whether error message is displayed" "${ret}" "requires a colon separator" + + rm -f "${TEST_WCURLRC}" +} + +testListSaveEmpty() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # List when no file exists + ret=$(wcurl --list-save 2>&1) + assertTrue "Verify whether '--list-save' exits successfully when file doesn't exist" "$?" + assertContains "Verify whether appropriate message is shown" "${ret}" "No saved calls found" + + rm -f "${TEST_WCURLRC}" +} + +testListSaveWithCalls() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save some calls + wcurl --save-call=quiet:"--silent --show-error" >/dev/null 2>&1 + wcurl --save-call=verbose:"--verbose" >/dev/null 2>&1 + + # List them + ret=$(wcurl --list-save 2>&1) + assertTrue "Verify whether '--list-save' exits successfully" "$?" + assertContains "Verify whether first saved call is listed" "${ret}" "quiet:" + assertContains "Verify whether second saved call is listed" "${ret}" "verbose:" + assertContains "Verify whether first call options are shown" "${ret}" "--silent --show-error" + assertContains "Verify whether second call options are shown" "${ret}" "--verbose" + + rm -f "${TEST_WCURLRC}" +} + +testRmSaveBasic() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save a call + wcurl --save-call=testcall:"--silent" >/dev/null 2>&1 + + # Remove it + ret=$(wcurl --rm-save=testcall 2>&1) + assertTrue "Verify whether '--rm-save' exits successfully" "$?" + assertContains "Verify whether removal message is displayed" "${ret}" "Removed saved call: testcall" + + # Verify it's gone + if [ -f "${TEST_WCURLRC}" ]; then + content=$(cat "${TEST_WCURLRC}") + assertNotContains "Verify whether call is removed from file" "${content}" "testcall=" + fi + + rm -f "${TEST_WCURLRC}" +} + +testRmSaveNonExistent() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save a call + wcurl --save-call=testcall:"--silent" >/dev/null 2>&1 + + # Try to remove non-existent call + ret=$(wcurl --rm-save=nonexistent 2>&1) + assertFalse "Verify whether '--rm-save' fails for non-existent call" "$?" + assertContains "Verify whether error message is displayed" "${ret}" "not found" + + rm -f "${TEST_WCURLRC}" +} + +testRunSaveBasic() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save a call without parameter expansion + wcurl --save-call=testrun:"--silent --show-error" >/dev/null 2>&1 + + # Run it with dry-run + ret=$(wcurl --dry-run --run-save=testrun example.com 2>&1) + assertTrue "Verify whether '--run-save' exits successfully" "$?" + assertContains "Verify whether saved options are applied" "${ret}" "--silent" + assertContains "Verify whether saved options are applied" "${ret}" "--show-error" + assertContains "Verify whether URL is included" "${ret}" "example.com" + + rm -f "${TEST_WCURLRC}" +} + +testRunSaveShortOption() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save a call + wcurl --save-call=testrun:"--verbose" >/dev/null 2>&1 + + # Run it with short option -r + ret=$(wcurl --dry-run -r=testrun example.com 2>&1) + assertTrue "Verify whether '-r' short option works" "$?" + assertContains "Verify whether saved options are applied with -r" "${ret}" "--verbose" + + rm -f "${TEST_WCURLRC}" +} + +testRunSaveNonExistent() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Try to run non-existent call (will treat as literal curl options) + ret=$(wcurl --dry-run --run-save=nonexistent example.com 2>&1) + assertTrue "Verify whether '--run-save' with non-existent name still works (treats as literal)" "$?" + assertContains "Verify whether the literal value is used" "${ret}" "nonexistent" + + rm -f "${TEST_WCURLRC}" +} + +testRunSaveParameterExpansion() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save a call with parameter expansion + wcurl --save-call=paramrun:'-o !1 !2' >/dev/null 2>&1 + + # Run it with parameters (this executes curl directly, not dry-run) + # We can't easily test this without actually running curl, so we'll skip the execution test + # Just verify the save worked + ret=$(cat "${TEST_WCURLRC}") + assertContains "Verify parameter expansion call is saved" "${ret}" "paramrun=-o !1 !2" + + rm -f "${TEST_WCURLRC}" +} + +testRunSaveParameterExpansionNotEnoughParams() +{ + TEST_WCURLRC="${SHUNIT_TMPDIR}/.wcurlrc" + export HOME="${SHUNIT_TMPDIR}" + rm -f "${TEST_WCURLRC}" + + # Save a call requiring 2 parameters + wcurl --save-call=needstwo:'-o !1 !2' >/dev/null 2>&1 + + # Try to run with only 1 parameter + ret=$(wcurl --run-save=needstwo oneparam 2>&1) + assertFalse "Verify whether '--run-save' fails when not enough parameters provided" "$?" + assertContains "Verify whether error message about parameters is shown" "${ret}" "Not enough parameters" + + rm -f "${TEST_WCURLRC}" +} + ## Ideas for tests: ## ## - URL with whitespace diff --git a/wcurl b/wcurl index c5dd6d1..07f9c5f 100755 --- a/wcurl +++ b/wcurl @@ -51,6 +51,8 @@ ${PROGRAM_NAME} -- a simple wrapper around curl to easily download files. Usage: ${PROGRAM_NAME} ... ${PROGRAM_NAME} [--curl-options ]... [--no-decode-filename] [-o|-O|--output ] [--dry-run] [--] ... ${PROGRAM_NAME} [--curl-options=]... [--no-decode-filename] [--output=] [--dry-run] [--] ... + ${PROGRAM_NAME} [--save-call=:]... [--list-save] [--rm-save=] + ${PROGRAM_NAME} [-r|--run-save=] ... ${PROGRAM_NAME} -h|--help ${PROGRAM_NAME} -V|--version @@ -69,6 +71,29 @@ Options: --dry-run: Do not actually execute curl, just print what would be invoked. + --save-call :: Save a curl call to $HOME/.wcurlrc without having to remember + option combinations, using a property value set; where the value + is the set of options to be reused, and the property is the name + to invoke the saved option combo. Name must contain only alphanumeric + characters, dashes, and underscores. Supports parameter expansion + using !1, !2, !3, etc. as placeholders that will be replaced with + actual values when invoked with -r or --run-save. When using + parameter expansion markers, enclose the curl options in single + quotes to prevent shell interpretation. + + -r, --run-save : Run a saved curl call from $HOME/.wcurlrc. For saved calls without + parameter expansion, the saved options are applied to the URLs provided. + For saved calls with parameter expansion markers (!1, !2, !3, etc.), you + must provide the exact number of parameters required by the highest marker + number. When using parameter expansion, only one --run-save call can be used + per command, and the command executes curl directly with the expanded options. + Parameters are provided as separate arguments following the --run-save option. + + --list-save: Output the name and the option combination for each name from $HOME/.wcurlrc + that have been saved for later reuse. + + --rm-save : Remove a saved curl call from $HOME/.wcurlrc. + -V, --version: Print version information. -h, --help: Print this usage message. @@ -123,9 +148,175 @@ readonly UNSAFE_PERCENT_ENCODE="%2F %5C" # Whether to invoke curl or not. DRY_RUN="false" +# Save-call name and options for --save-call +SAVE_CALL_NAME="" +SAVE_CALL_OPTIONS="" +HAS_SAVE_CALLS="false" + +# Save a curl call to .wcurlrc +save_call() +{ + if [ -z "${SAVE_CALL_NAME}" ]; then + error "No save-call name provided." + fi + + # Validate name: only allow alphanumeric, dash, underscore + case "${SAVE_CALL_NAME}" in + *[!a-zA-Z0-9_-]*) + error "Invalid characters in save-call name. Use only letters, numbers, dash, and underscore." + ;; + esac + + if [ -z "${SAVE_CALL_OPTIONS}" ]; then + error "No options provided for save-call." + fi + + wcurlrc="${HOME}/.wcurlrc" + + # Create .wcurlrc if it doesn't exist with secure permissions + if [ ! -f "${wcurlrc}" ]; then + touch "${wcurlrc}" + chmod 600 "${wcurlrc}" + else + # Fix permissions on existing file if they're too permissive + current_perms=$(stat -c '%a' "${wcurlrc}" 2>/dev/null || stat -f '%Lp' "${wcurlrc}" 2>/dev/null || echo "600") + if [ "${current_perms}" != "600" ]; then + chmod 600 "${wcurlrc}" + fi + fi + + # Escape the name for use in grep pattern (escape regex metacharacters) + escaped_name=$(printf "%s" "${SAVE_CALL_NAME}" | sed 's/[.\[^$*]/\\&/g') + + # Check if name already exists and update it, otherwise append + if grep -q "^${escaped_name}=" "${wcurlrc}" 2>/dev/null; then + # Update existing entry using a secure temp file + temp_file=$(mktemp "${wcurlrc}.XXXXXX") || error "Failed to create temp file" + chmod 600 "${temp_file}" + grep -v "^${escaped_name}=" "${wcurlrc}" > "${temp_file}" 2>/dev/null || true + printf "%s=%s\n" "${SAVE_CALL_NAME}" "${SAVE_CALL_OPTIONS}" >> "${temp_file}" + mv "${temp_file}" "${wcurlrc}" + else + # Append new entry + printf "%s=%s\n" "${SAVE_CALL_NAME}" "${SAVE_CALL_OPTIONS}" >> "${wcurlrc}" + fi + + printf "Saved call '%s' with options: %s\n" "${SAVE_CALL_NAME}" "${SAVE_CALL_OPTIONS}" +} + +# Load saved call from .wcurlrc +get_saved_call() +{ + name="${1}" + wcurlrc="${HOME}/.wcurlrc" + + if [ ! -f "${wcurlrc}" ]; then + return 1 + fi + + # Look for the saved call + saved_options=$(grep "^${name}=" "${wcurlrc}" 2>/dev/null | sed "s/^${name}=//") + + if [ -n "${saved_options}" ]; then + printf "%s" "${saved_options}" + return 0 + fi + + return 1 +} + +# List all saved calls from .wcurlrc +list_saved_calls() +{ + wcurlrc="${HOME}/.wcurlrc" + + if [ ! -f "${wcurlrc}" ]; then + printf "No saved calls found. File does not exist: %s\n" "${wcurlrc}" + exit 0 + fi + + if [ ! -s "${wcurlrc}" ]; then + printf "No saved calls found in: %s\n" "${wcurlrc}" + exit 0 + fi + + printf "Saved calls in %s:\n\n" "${wcurlrc}" + + # Read and display each saved call + while IFS= read -r line; do + # Skip empty lines and comments + case "${line}" in + ''|'#'*) continue ;; + esac + + # Validate that the line contains an equals sign and has a valid name + case "${line}" in + *=*) + # Extract name and check if it's valid (starts with alphanumeric) + name=$(printf "%s" "${line}" | cut -d= -f1) + case "${name}" in + [a-zA-Z0-9]*) + # Valid entry + options=$(printf "%s" "${line}" | cut -d= -f2-) + printf " %s:\n %s\n\n" "${name}" "${options}" + ;; + *) + # Invalid name (starts with space, quote, etc.), skip + continue + ;; + esac + ;; + *) + # No equals sign, skip + continue + ;; + esac + done < "${wcurlrc}" + + exit 0 +} + +# Remove a saved call from .wcurlrc +remove_call() +{ + if [ -z "${1}" ]; then + error "No call name provided to remove." + fi + + name="${1}" + wcurlrc="${HOME}/.wcurlrc" + + if [ ! -f "${wcurlrc}" ]; then + error "No saved calls found. File does not exist: ${wcurlrc}" + fi + + # Escape the name for use in grep pattern + escaped_name=$(printf "%s" "${name}" | sed 's/[.\[^$*]/\\&/g') + + # Check if the call exists + if ! grep -q "^${escaped_name}=" "${wcurlrc}" 2>/dev/null; then + error "Saved call '${name}' not found in ${wcurlrc}" + fi + + # Remove the entry using a secure temp file + temp_file=$(mktemp "${wcurlrc}.XXXXXX") || error "Failed to create temp file" + chmod 600 "${temp_file}" + grep -v "^${escaped_name}=" "${wcurlrc}" > "${temp_file}" 2>/dev/null || true + mv "${temp_file}" "${wcurlrc}" + + printf "Removed saved call: %s\n" "${name}" + exit 0 +} + # Sanitize parameters. sanitize() { + # If we only had save-calls and no URLs, exit successfully + if [ "${HAS_SAVE_CALLS}" = "true" ] && [ -z "${URLS}" ]; then + printf "Saved to: %s\n" "${HOME}/.wcurlrc" + exit 0 + fi + if [ -z "${URLS}" ]; then error "You must provide at least one URL to download." fi @@ -272,6 +463,150 @@ exec_curl() # Default to decoding the output filename DECODE_FILENAME="true" +# Preprocess arguments to expand -r/--run-save options to --curl-options (non-parameter expansion case only) +preprocess_run_options() +{ + result="" + + while [ "$#" -gt 0 ]; do + case "${1}" in + -r=*|--run-save=*) + run_value=$(printf "%s\n" "${1}" | sed 's/^\(-r\|--run-save\)=//') + saved=$(get_saved_call "${run_value}" 2>/dev/null || true) + + if [ -n "${saved}" ]; then + result="${result} --curl-options='${saved}'" + else + result="${result} --curl-options='${run_value}'" + fi + shift + ;; + + *) + result="${result} '${1}'" + shift + ;; + esac + done + + printf "%s" "${result}" +} + +# Preprocess arguments if -r or --run-save is present +# First check if -r/--run-save uses parameter expansion and handle it directly +for arg in "$@"; do + case "${arg}" in + -r=*|--run-save=*) + run_value=$(printf "%s\n" "${arg}" | sed 's/^\(-r\|--run-save\)=//') + saved=$(get_saved_call "${run_value}" 2>/dev/null || true) + + if [ -n "${saved}" ] && printf "%s" "${saved}" | grep -q '!'; then + # This uses parameter expansion - check for --dry-run first + has_dry_run="false" + for check_arg in "$@"; do + case "${check_arg}" in + --dry-run) + has_dry_run="true" + break + ;; + esac + done + + # Remove arguments up to and including --run-save=name + while [ "$#" -gt 0 ]; do + current_arg="${1}" + shift + case "${current_arg}" in + -r=*|--run-save=*) + # Found and removed the --run-save arg, now collect parameters + break + ;; + esac + done + + # Find the highest parameter number + max_param=0 + i=1 + while [ "${i}" -le 99 ]; do + if printf "%s" "${saved}" | grep -q "!${i}"; then + max_param="${i}" + fi + i=$((i + 1)) + done + + # Collect parameters (skip --dry-run if present, it was already removed above) + param_count=0 + while [ "${param_count}" -lt "${max_param}" ] && [ "$#" -gt 0 ]; do + case "${1}" in + --*) + break + ;; + *) + param_count=$((param_count + 1)) + eval "param_${param_count}='${1}'" + shift + ;; + esac + done + + # Validate + if [ "${param_count}" -lt "${max_param}" ]; then + error "Not enough parameters for --run=${run_value}. Expected at least ${max_param} parameters, got ${param_count}." + fi + + # Perform substitution - build curl command array safely + # We need to parse the saved options and substitute parameters + # Process in REVERSE order (largest numbers first) to avoid !1 matching !10, !11, etc. + expanded_opts="${saved}" + i="${max_param}" + while [ "${i}" -ge 1 ]; do + # shellcheck disable=SC2154 + eval "param_value=\${param_${i}}" + # Use word boundary to match exact parameter number + # Match !N followed by non-digit or end of string + # First replace with unique marker + marker="__WCURL_PARAM_${i}__" + expanded_opts=$(printf "%s" "${expanded_opts}" | sed "s|!${i}\([^0-9]\)|${marker}\1|g; s|!${i}$|${marker}|g") + # Then replace marker with escaped value (escape single quotes for shell) + # shellcheck disable=SC2154 + param_escaped=$(printf "%s" "${param_value}" | sed "s/'/'\\\\''/g") + expanded_opts=$(printf "%s" "${expanded_opts}" | sed "s|${marker}|'${param_escaped}'|g") + i=$((i - 1)) + done + + # Execute curl directly - use sh -c with proper quoting to avoid eval + # If --dry-run was detected, just show the command instead + if [ "${has_dry_run}" = "true" ]; then + printf "curl %s\n" "${expanded_opts}" + exit 0 + else + sh -c "curl ${expanded_opts}" + exit $? + fi + fi + ;; + esac +done + +# If we get here, no parameter expansion was found - proceed with normal preprocessing +has_run_option="false" +for arg in "$@"; do + case "${arg}" in + -r=*|--run-save=*) + has_run_option="true" + break + ;; + esac +done + +if [ "${has_run_option}" = "true" ]; then + # Expand -r/--run-save options to --curl-options (non-parameter expansion case) + expanded_args=$(preprocess_run_options "$@") + # Reset positional parameters + # shellcheck disable=SC2086 + eval set -- ${expanded_args} +fi + # Use "${1-}" in order to avoid errors because of 'set -u'. while [ -n "${1-}" ]; do case "${1}" in @@ -311,6 +646,62 @@ while [ -n "${1-}" ]; do DECODE_FILENAME="false" ;; + --save-call=*) + opt=$(printf "%s\n" "${1}" | sed 's/^--save-call=//') + + # Check if the format contains a colon + case "${opt}" in + *:*) + SAVE_CALL_NAME=$(printf "%s\n" "${opt}" | cut -d: -f1) + SAVE_CALL_OPTIONS=$(printf "%s\n" "${opt}" | cut -d: -f2-) + + # Check if there are unquoted spaces in options (indicates missing quotes) + # This happens when shell splits the argument + if [ -z "${SAVE_CALL_OPTIONS}" ]; then + error "--save-call options cannot be empty. Use format: --save-call=NAME:\"CURL_OPTIONS\"" + fi + ;; + *) + error "--save-call requires a colon separator. Use format: --save-call=NAME:\"CURL_OPTIONS\"" + ;; + esac + + HAS_SAVE_CALLS="true" + save_call + # Reset for next potential save-call + SAVE_CALL_NAME="" + SAVE_CALL_OPTIONS="" + ;; + + --save-call) + error "--save-call requires a value in the format: --save-call=NAME:\"CURL_OPTIONS\"" + ;; + + --list-save) + list_saved_calls + ;; + + --rm-save=*) + opt=$(printf "%s\n" "${1}" | sed 's/^--rm-save=//') + remove_call "${opt}" + ;; + + --rm-save) + error "--rm-save requires a value. Use format: --rm-save=NAME" + ;; + + # Catch unquoted save-call attempts that got split by the shell + -R | -z | -L) + # Check if the previous argument pattern suggests a malformed --save-call + # This is a heuristic - if we see curl options appearing as separate arguments + # early in parsing before any URLs, it might be a quote issue + if [ -z "${URLS}" ] && [ "${HAS_SAVE_CALLS}" = "true" ]; then + error "Detected unquoted curl options. When using --save-call with multiple options, quote them: --save-call=NAME:\"-R -z -L\"" + fi + # Otherwise treat as unknown option + error "Unknown option: '$1'." + ;; + -h | --help) usage exit 0 diff --git a/wcurl.1 b/wcurl.1 index 5199504..6706661 100644 --- a/wcurl.1 +++ b/wcurl.1 @@ -9,6 +9,10 @@ \fBwcurl [\--curl\-options=]... [\--dry\-run] [\--no\-decode\-filename] [\--output=] [\--] ...\fP +\fBwcurl [\--save\-call=:]... [\--list\-save] [\--rm\-save=]\fP + +\fBwcurl [\-r|\--run\-save=] ...\fP + \fBwcurl \-V|\--version\fP \fBwcurl \-h|\--help\fP @@ -71,6 +75,29 @@ Do not percent\-decode the output filename, even if the percent\-encoding in the URL was done by \fBwcurl\fP, e.g.: The URL contained whitespace. .IP --dry-run Do not actually execute curl, just print what would be invoked. +.IP --save-call=\:\... +Save a curl call to \fI$HOME/.wcurlrc\fP without having to remember option +combinations, using a property value set; where the value is the set of options +to be reused, and the property is the name to invoke the saved option combo. +The name must contain only alphanumeric characters, dashes, and underscores for +security reasons. Supports parameter expansion using \fI!1\fP, \fI!2\fP, \fI!3\fP, etc. as placeholders that +will be replaced with actual values when invoked with \fB\-r\fP or \fB\--run\-save\fP. +When using parameter expansion markers, enclose the curl options in single quotes +to prevent shell interpretation. The \fI.wcurlrc\fP file is created with permissions +600 (owner read/write only) for security. +.IP "-r, --run-save, --run-save=\" +Run a saved curl call from \fI$HOME/.wcurlrc\fP. For saved calls without parameter +expansion, the saved options are applied to the URLs provided. For saved calls +with parameter expansion markers (\fI!1\fP, \fI!2\fP, \fI!3\fP, etc.), you must provide the +exact number of parameters required by the highest marker number. When using +parameter expansion, only one \fB\--run\-save\fP call can be used per command, and +the command executes curl directly with the expanded options. Parameters are +provided as separate arguments following the \fB\--run\-save\fP option. +.IP --list-save +Output the name and the option combination for each name from \fI$HOME/.wcurlrc\fP +that have been saved for later reuse. +.IP --rm-save=\ +Remove a saved curl call from \fI$HOME/.wcurlrc\fP. .IP "-V, \--version" Print version information. .IP "-h, \--help" @@ -102,6 +129,40 @@ Download a file passing the \fB\--progress\-bar\fP and \fB\--http2\fP flags to c Download multiple files without a limit of concurrent connections per host (the default limit is 5): \fBwcurl \--curl\-options="\--parallel\-max\-host 0" example.com/filename1.txt example.com/filename2.txt\fP + +Save simple curl option combinations to be reused later: + +\fBwcurl \--save\-call=quiet:"\--silent \--show\-error" \--save\-call=progress:"\--progress\-bar \-L \-v"\fP + +Save a curl option combination with parameter expansion (use !1, !2, !3 for placeholders, and enclose in single quotes): + +\fBwcurl \--save\-call=dumpVerb:'\-I !1 \-o !2 \--next \-v !3 \-o !4'\fP + +\fBwcurl \--save\-call=getFile:'\-o !1 !2'\fP + +List all saved curl option combinations: + +\fBwcurl \--list\-save\fP + +Remove a saved curl option combination: + +\fBwcurl \--rm\-save=quiet\fP + +Use a saved curl option combination without parameter expansion: + +\fBwcurl \--run\-save="quiet" example.com/file1.txt\fP + +\fBwcurl \-r="progress" example.com/file1.txt example.com/file2.txt\fP + +Use a saved curl option combination with parameter expansion. The number of parameters after the name must match the highest marker (!1, !2, etc.): + +\fBwcurl \--run\-save="dumpVerb" example.com dump.txt example.com/file.txt file.txt\fP + +This expands to: \fIcurl \-I example.com \-o dump.txt \--next \-v example.com/file.txt \-o file.txt\fP + +\fBwcurl \-r="getFile" output.html example.com/index.html\fP + +This expands to: \fIcurl \-o output.html example.com/index.html\fP .SH AUTHORS .nf Samuel Henrique \ diff --git a/wcurl.md b/wcurl.md index 6b214e7..3c38d6f 100644 --- a/wcurl.md +++ b/wcurl.md @@ -22,6 +22,10 @@ Added-in: n/a **wcurl [--curl-options=\]... [--dry-run] [--no-decode-filename] [--output=\] [--] \...** +**wcurl [--save-call=\:\]... [--list-save] [--rm-save=\]** + +**wcurl [-r|--run-save=\] \...** + **wcurl -V|--version** **wcurl -h|--help** @@ -94,6 +98,38 @@ URL was done by **wcurl**, e.g.: The URL contained whitespace. Do not actually execute curl, just print what would be invoked. +## --save-call=\:\... + +Save a curl call to `$HOME/.wcurlrc` without having to remember option +combinations, using a property value set; where the value is the set of options +to be reused, and the property is the name to invoke the saved option combo. +The name must contain only alphanumeric characters, dashes, and underscores for +security reasons. Supports parameter expansion using `!1`, `!2`, `!3`, etc. as +placeholders that will be replaced with actual values when invoked with **-r** +or **--run-save**. When using parameter expansion markers, enclose the curl +options in single quotes to prevent shell interpretation. The `.wcurlrc` file is +created with permissions 600 (owner read/write only) for security. + + +## -r, --run-save, --run-save=\ + +Run a saved curl call from `$HOME/.wcurlrc`. For saved calls without parameter +expansion, the saved options are applied to the URLs provided. For saved calls +with parameter expansion markers (`!1`, `!2`, `!3`, etc.), you must provide the +exact number of parameters required by the highest marker number. When using +parameter expansion, only one **--run-save** call can be used per command, and +the command executes curl directly with the expanded options. Parameters are +provided as separate arguments following the **--run-save** option. + +## --list-save + +Output the name and the option combination for each name from `$HOME/.wcurlrc` +that have been saved for later reuse. + +## --rm-save=\ + +Remove a saved curl call. + ## -V, \--version Print version information. @@ -135,6 +171,40 @@ Download multiple files without a limit of concurrent connections per host (the **wcurl --curl-options="--parallel-max-host 0" example.com/filename1.txt example.com/filename2.txt** +Save simple curl option combinations to be reused later: + +**wcurl --save-call=quiet:"--silent --show-error" --save-call=progress:"--progress-bar -L -v"** + +Save a curl option combination with parameter expansion (use !1, !2, !3 for placeholders, and enclose in single quotes): + +**wcurl --save-call=dumpVerb:'-I !1 -o !2 --next -v !3 -o !4'** + +**wcurl --save-call=getFile:'-o !1 !2'** + +List all saved curl option combinations: + +**wcurl --list-save** + +Remove a saved curl option combination: + +**wcurl --rm-save=quiet** + +Use a saved curl option combination without parameter expansion: + +**wcurl --run-save="quiet" example.com/file1.txt** + +**wcurl -r="progress" example.com/file1.txt example.com/file2.txt** + +Use a saved curl option combination with parameter expansion. The number of parameters after the name must match the highest marker (!1, !2, etc.): + +**wcurl --run-save="dumpVerb" example.com dump.txt example.com/file.txt file.txt** + +This expands to: `curl -I example.com -o dump.txt --next -v example.com/file.txt -o file.txt` + +**wcurl -r="getFile" output.html example.com/index.html** + +This expands to: `curl -o output.html example.com/index.html` + # AUTHORS Samuel Henrique \