Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

[PATCH] (2nd try) new function passphrase generator



Taking Mikael's feedback into consideration, I've reworked the function and renamed it zgenpassphrase. I also got the completion system working so I was able to test the completion function. (zshcompsys is a mess and examples are limited.)


diff --git a/Completion/Zsh/Function/_zgenpassphrase b/Completion/Zsh/Function/_zgenpassphrase

new file mode 100644
index 000000000..d1c9793a2
--- /dev/null
+++ b/Completion/Zsh/Function/_zgenpassphrase
@@ -0,0 +1,21 @@
+#compdef zgenpassphrase
+
+# Zsh completion function for the 'zgenpassphrase' command.
+#
+
+local -a context
+local curcontext="$curcontext"
+local -A opt_args
+
+_arguments -C \
+  '(-h --help)'{-h,--help}'[Display help message]' \
+  '(-c --capitalize)'{-c,--capitalize}'[Capitalize the first letter of each word]' \ +  '(-n --num-words)'{-n+,--num-words=}'[Specify the number of words (default: 6,min 2)]:number of words:' \ +  '(-s --separator)'{-s+,--separator=}'[Use SEP as the word separator (default: " ")]:separator string:' \ +  '(-d --digits)'{-d+,--digits}'[Include random digits (optional COUNT)]:number of digits:' \ +  '(-f --file -p --add-search-path -P --override-path -w --wordlist-name)'{-f+,--file=}'[Use a specific wordlist file (bypasses searching)]:wordlist file:_files' \ +  '(-w --wordlist-name -f --file)'{-w+,--wordlist-name=}'[Search for a different file NAME in default paths]:wordlist name:' \
+  \
+  '(-P --override-path -f --file)*'{-p+,--add-search-path=}'[Prepend a directory to the search path]:directory:_files -/' \ +  '(-p --add-search-path -f --file)*'{-P+,--override-path=}'[Override the default search path]:directory:_files -/'
+
diff --git a/Functions/Misc/zgenpassphrase b/Functions/Misc/zgenpassphrase
new file mode 100644
index 000000000..e22cc44b7
--- /dev/null
+++ b/Functions/Misc/zgenpassphrase
@@ -0,0 +1,229 @@
+#!/usr/bin/zsh
+# diceware - Generates a diceware style passphrase
+#
+emulate -L zsh # Ensure Zsh option defaults and FUNCTION_ARGZERO are set
+
+local wordlist_file="eff_large_wordlist.txt"
+local -a wordlist_search_paths=(
+  '.'
+  '${ZDOTDIR:-$HOME}'
+)
+local usage
+read -rd '' usage << EOU
+ Usage: $0 [options]
+
+ Options:
+   -c, --capitalize
+       Capitalize the first letter of each word.
+   -s SEP, --separator SEP
+       Use SEP as the word separator (default: " ").
+   -d COUNT, --digits COUNT
+       Include COUNT random digits (0-9) pasted to random words.
+   -n N, --num-words N
+       Specify the number of words in the passphrase (defaults to 6, minimum 2).
+   -f FILE, --file FILE
+       Specify the diceware wordlist.
+       If omitted, '${wordlist_file}' is searched for in:
+          ${(pj:\n           :)wordlist_search_paths}
+   -w WORDLIST_FILENAME, --wordlist-file WORDLIST_FILENAME
+       The wordlist file name to search for in the paths
+   -p SEARCH_PATH, --add-search-path SEARCH_PATH
+       Additional paths to prepend to the default search path.
+       May be specified multiple times.
+   -P SEARCH_PATH, --override-path SEARCH_PATH
+       Paths to replace default search paths.
+       May be specified multiple times.
+   -h, --help             : This message
+
+ Examples:
+# 6 words, searches for wordlist, no options
+   $0
+# 8 words, searches for wordlist
+   $0 -n 8
+# 6 words, custom wordlist path
+   $0 --file /my/list.txt
+# Capitalize, hyphen-separated, 7 words
+   $0 -c --separator '-' -n 7
+# 6 words, 1 number pasted to a random word
+   $0 -d 1
+# 6 words, 3 numbers pasted to random words
+   $0 --digits 3
+# Capitalize, dot-separated, 2 numbers, 5 words
+   $0 -c -s'.' -N 2 -n 5
+EOU
+#
+# Requires: zsh/random module, zsh/zutil, sed, and wc
+
+if ! zmodload zsh/random; then
+  print -u2 "Error: zsh/random module could not be loaded. Is it installed and available?"
+  return 1
+fi
+if ! zmodload zsh/zutil; then
+  print -u2 "Error: zsh/zutil module could not be loaded. Is it installed and available?"
+  return 1
+fi
+
+
+# Declare option variables with default values
+integer capitalize=0
+local separator=" "
+integer num_to_add_to_words=0
+integer num_words=6
+
+# zparseopts setup for short and long options
+local -A parsed_opts # Associative array to hold parsed options
+local -a additional_paths override_paths
+
+zparseopts -E -D -M -A parsed_opts \
+  c='-capitalize' -capitalize \
+  s:='-separator' -separator: \
+  d:='-digits' -digits: \
+  n:='-num-words' -num-words: \
+  f:='-file' -file: \
+  w:='-wordlist-file' -wordlist-file: \
+  h='-help' "'?'"='-help' -help \
+  p+:='-add-search-path' -add-search-path+:=additional_paths \
+  P+:='-override-path' -override-path+:=override_paths \
+  || { print -ru2 -- "$usage" ; return 1; }
+
+# Process parsed options
+local opt_name opt_val wordlist_path
+
+for opt_name opt_val in "${(@kv)parsed_opts}"; do
+  # Handle --opt-name= by stripping first = unless it is the entire value
+  if [[ $opt_val =~ '^=.*' && $#opt_val -gt 1 ]]; then
+    opt_val="${opt_val#=}"
+  fi
+  case "$opt_name" in
+    --capitalize)
+      capitalize=1
+      ;;
+    --separator)
+      separator="${opt_val}"
+      ;;
+    --digits)
+      # Check if an argument was provided for -N/--numbers
+      if [[ -z "${opt_val}" ]]; then
+        num_to_add_to_words=1
+      else
+        num_to_add_to_words="${opt_val}"
+      fi
+      ;;
+    --num-words)
+      num_words="${opt_val}"
+      ;;
+    --file)
+      wordlist_path="${opt_val}"
+      ;;
+    --wordlist-file)
+      wordlist_file="${opt_val}"
+      ;;
+    --add-search-path)
+      wordlist_search_paths=( "${additional_paths[@]}" "${wordlist_search_paths[@]}")
+      ;;
+    --override-path)
+      wordlist_search_paths=( "${override_paths[@]}")
+      ;;
+    --help)
+      print -u2 $usage
+      return 0
+      ;;
+    *)
+      print -u2 $usage
+      return 1
+      ;;
+  esac
+done
+
+if (( num_words < 2 )); then
+  print -u2 "Error: Minimum number of words is 2."
+  print -u2 "$usage"
+  return 1
+fi
+
+# Determine wordlist file if not explicitly specified
+if [[ -z "$wordlist_path" ]]; then
+  for p in "${(@e)wordlist_search_paths}"; do
+    if [[ -f "$p/$wordlist_file" ]]; then
+      wordlist_path="$p/$wordlist_file"
+      break
+    fi
+  done
+
+  if [[ -z "$wordlist_path" ]]; then
+    print -ru2  -- "
+Error: Diceware wordlist '$wordlist_file' not found in any search path.
+
+Please download the official wordlist from the Electronic Frontier Foundation +and place it in one of the searched directories (e.g., \$ZDOTDIR or \$HOME).
+
+Canonical URL: https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt
+
+Alternatively, specify a custom path using the -f or --file option."
+    return 1
+  fi
+fi
+
+if [[ -n "$separator" && "$separator" =~ [[:cntrl:]] ]]; then
+  print -ru2 -- "Warning: Using a non-printable character as a separator can produce unexpected output."
+fi
+
+integer num_lines=$(wc -l < "$wordlist_path")
+if (( num_lines == 0 )); then
+  print -ru2 -- "Error: Wordlist is empty."
+  print -ru2 -- "$usage"
+  return 1
+fi
+
+local -a line_numbers=()
+integer i
+for i in {1..$num_words}
+do
+  line_numbers+=( $(( zrand_int($num_lines,1,1) )) )
+done
+
+local sed_command=""
+if (( num_words > 0 )); then
+    local -a tmp_sed_parts=(${^${(n)line_numbers}}p)
+    sed_command="${(j:;:)tmp_sed_parts}"
+fi
+
+local -a raw_words
+if [[ -n "$sed_command" ]]; then
+       raw_words=( ${(f)"$(sed -n "$sed_command" "$wordlist_path")"} )
+else
+    raw_words=() # No words to fetch if num_words was 0
+fi
+
+if (( ${#raw_words} == 0 )); then
+  print -ru2 -- "Error: sed returned no words"
+  return 1
+fi
+
+local -a passphrase_words=()
+local  word
+for  word in "${raw_words[@]}"; do
+  passphrase_words+=( "${${(Az)word}[-1]}" )
+done
+
+# Paste random numbers to words if requested
+if (( num_to_add_to_words > 0 && ${#passphrase_words[@]} > 0 )); then
+  for i in {1..$num_to_add_to_words}
+  do
+    integer random_digit=$(( zrand_int(10) ))
+    # Use zrand_int with (upper, lower, inclusive) for 1-based array indexing
+    # directly
+    integer target_word_index=$(( zrand_int(${#passphrase_words},1,1) ))
+    integer paste_position=$(( zrand_int(2) ))
+
+    if (( paste_position == 0 )); then
+ passphrase_words[target_word_index]="${random_digit}${passphrase_words[target_word_index]}" # Prefix
+    else
+ passphrase_words[target_word_index]="${passphrase_words[target_word_index]}${random_digit}" # Suffix
+    fi
+  done
+fi
+
+local final_passphrase="${(pj:$separator:)passphrase_words}"
+
+print -r -- "$final_passphrase"






Messages sorted by: Reverse Date, Date, Thread, Author