| #!/bin/sh |
| |
| # Packages data |
| # |
| # Notes: |
| # * python3-codecs: Also contains codecs for CJK encodings but we don't |
| # have a good way to check for uses |
| # * python3-openssl: Don't include hashlib as it supports several |
| # standard algorithms without requiring OpenSSL |
| |
| packages=" |
| python3-asyncio: asyncio |
| python3-cgi: cgi |
| python3-cgitb: cgitb |
| python3-codecs: unicodedata |
| python3-ctypes: ctypes |
| python3-dbm: dbm dbm.dumb dbm.gnu dbm.ndbm |
| python3-decimal: decimal |
| python3-distutils: distutils |
| python3-email: email |
| python3-logging: logging |
| python3-lzma: lzma |
| python3-multiprocessing: multiprocessing |
| python3-ncurses: ncurses |
| python3-openssl: ssl |
| python3-pydoc: doctest pydoc |
| python3-readline: readline |
| python3-sqlite3: sqlite3 |
| python3-unittest: unittest |
| python3-urllib: urllib |
| python3-uuid: uuid |
| python3-xml: xml xmlrpc |
| " |
| |
| |
| # Constants |
| |
| stdin_name="<stdin>" |
| grep_dir_filters=" |
| -Ir |
| --include=*.py |
| --exclude=setup.py |
| --exclude=test_*.py |
| --exclude=*_test.py |
| --exclude-dir=test |
| --exclude-dir=tests |
| --exclude-dir=ipkg-* |
| --exclude-dir=.pkgdir |
| " |
| |
| log_level_notice=5 |
| log_level_info=6 |
| log_level_debug=7 |
| |
| # /usr/include/sysexits.h |
| ex_usage=64 |
| ex_noinput=66 |
| ex_unavailable=69 |
| ex_software=70 |
| |
| newline=" |
| " |
| oifs="$IFS" |
| |
| |
| # Defaults |
| |
| grep_output_default_max_count=3 |
| grep_output_default_color_when="auto" |
| grep_output_default_line_prefix="-HnT --label=$stdin_name" |
| grep_output_default_context_num=1 |
| |
| log_level_default="$log_level_info" |
| |
| |
| # Global variables |
| |
| log_level= |
| grep= |
| grep_output_options= |
| grep_output_description= |
| stdin= |
| output_name= |
| is_first_search= |
| |
| |
| # Logging |
| |
| log() { |
| printf '%s\n' "$*" |
| } |
| |
| can_log_notice() { |
| [ "$log_level" -ge "$log_level_notice" ] |
| } |
| |
| can_log_info() { |
| [ "$log_level" -ge "$log_level_info" ] |
| } |
| |
| can_log_debug() { |
| [ "$log_level" -ge "$log_level_debug" ] |
| } |
| |
| log_notice() { |
| if can_log_notice; then |
| log "$@" |
| fi |
| } |
| |
| log_info() { |
| if can_log_info; then |
| log "$@" |
| fi |
| } |
| |
| log_debug() { |
| if can_log_debug; then |
| log "$@" |
| fi |
| } |
| |
| log_error() { |
| printf '%s\n' "Error: $*" >&2 |
| } |
| |
| |
| # Usage |
| |
| usage() { |
| cat <<- EOF |
| Usage: ${0##*/} [OPTION]... [FILE]... |
| Search for imports of certain Python standard libraries in each FILE, |
| generate a list of suggested package dependencies. |
| |
| With no FILE, or when FILE is -, read standard input. |
| |
| Options: |
| -c WHEN use color in output; |
| WHEN is 'always', 'never', or 'auto' (default: '$grep_output_default_color_when') |
| -h display this help text and exit |
| -m NUM show max NUM matches per package per file (default: $grep_output_default_max_count); |
| use 0 to show all matches |
| -n NAME when one or no FILE is given, use NAME instead of FILE in |
| displayed information |
| -q show suggested dependencies only |
| -v show verbose output (also show all matches) |
| -x NUM show NUM lines of context (default: $grep_output_default_context_num) |
| |
| EOF |
| } |
| |
| |
| # Imports search |
| |
| get_package_modules() { |
| local line="$1" |
| local field_num=0 |
| local IFS=: |
| |
| for field in $line; do |
| # strip leading and trailing whitespace |
| field="${field#"${field%%[! ]*}"}" |
| field="${field%"${field##*[! ]}"}" |
| |
| # set variables in search_path() |
| if [ "$field_num" -eq 0 ]; then |
| package="$field" |
| field_num=1 |
| elif [ "$field_num" -eq 1 ]; then |
| modules="$field" |
| field_num=2 |
| else |
| field_num=3 |
| fi |
| done |
| |
| if [ "$field_num" -ne 2 ] || [ -z "$package" ] || [ -z "$modules" ]; then |
| log_error "invalid package data \"$line\"" |
| exit "$ex_software" |
| fi |
| } |
| |
| search_path_for_modules() { |
| local path="$1" |
| local path_is_dir="$2" |
| local path_is_stdin="$3" |
| local package="$4" |
| local modules="$5" |
| local modules_regex |
| local regex |
| local remove_dir_prefix |
| local grep_results |
| local IFS="$oifs" |
| |
| log_debug " Looking for modules in $package ($modules)" |
| |
| modules_regex=$(printf '%s' "$modules" | sed -e 's/\./\\./g' -e 's/\s\+/|/g') |
| regex="\b(import\s+($modules_regex)|from\s+($modules_regex)\S*\s+import)\b" |
| |
| if [ -n "$path_is_dir" ]; then |
| remove_dir_prefix="s|^\(\(\x1b[[0-9;]*[mK]\)*\)$path|\1|" |
| grep_results=$($grep $grep_output_options $grep_dir_filters -E "$regex" "$path") |
| |
| elif [ -n "$path_is_stdin" ]; then |
| grep_results=$(printf '%s\n' "$stdin" | $grep $grep_output_options -E "$regex") |
| |
| else |
| grep_results=$($grep $grep_output_options -E "$regex" "$path") |
| fi |
| |
| if [ "$?" -ne 0 ]; then |
| log_debug " No imports found" |
| log_debug "" |
| return 0 |
| fi |
| |
| log_info " Found imports for: $modules" |
| |
| if can_log_info; then |
| printf '%s\n' "$grep_results" | sed -e "$remove_dir_prefix" -e "s/^/ /" |
| fi |
| |
| log_info "" |
| |
| # set variable in search_path() |
| suggested="$suggested +$package" |
| } |
| |
| search_path() { |
| local path="$1" |
| local name="$2" |
| local path_is_dir |
| local path_is_stdin |
| local package |
| local modules |
| local suggested |
| local IFS="$newline" |
| |
| if [ "$path" = "-" ]; then |
| path_is_stdin=1 |
| |
| else |
| if ! [ -e "$path" ]; then |
| log_error "$path does not exist" |
| exit "$ex_noinput" |
| fi |
| |
| if [ -d "$path" ]; then |
| path="${path%/}/" |
| path_is_dir=1 |
| fi |
| fi |
| |
| log_info "" |
| log_info "Searching $name (showing $grep_output_description):" |
| log_info "" |
| |
| if [ -n "$path_is_stdin" ]; then |
| stdin="$(cat)" |
| fi |
| |
| for line in $packages; do |
| # strip leading whitespace |
| line="${line#"${line%%[! ]*}"}" |
| |
| # skip blank lines or comments (beginning with #) |
| if [ -z "$line" ] || [ "$line" != "${line###}" ]; then |
| continue |
| fi |
| |
| package= |
| modules= |
| |
| get_package_modules "$line" |
| search_path_for_modules "$path" "$path_is_dir" "$path_is_stdin" "$package" "$modules" |
| done |
| |
| log_notice "Suggested dependencies for $name:" |
| |
| if [ -z "$suggested" ]; then |
| suggested="(none)" |
| fi |
| IFS="$oifs" |
| for package in $suggested; do |
| log_notice " $package" |
| done |
| |
| log_notice "" |
| } |
| |
| |
| # Find GNU grep |
| |
| if ggrep --version 2>&1 | grep -q GNU; then |
| grep="ggrep" |
| elif grep --version 2>&1 | grep -q GNU; then |
| grep="grep" |
| else |
| log_error "cannot find GNU grep" |
| exit "$ex_unavailable" |
| fi |
| |
| |
| # Process environment variables |
| |
| case $PYTHON3_FIND_STDLIB_DEPENDS_LOG_LEVEL in |
| notice) |
| log_level="$log_level_notice" |
| ;; |
| info) |
| log_level="$log_level_info" |
| ;; |
| debug) |
| log_level="$log_level_debug" |
| ;; |
| *) |
| log_level="$log_level_default" |
| ;; |
| esac |
| |
| grep_output_max_count="${PYTHON3_FIND_STDLIB_DEPENDS_MAX_COUNT:-$grep_output_default_max_count}" |
| grep_output_color_when="${PYTHON3_FIND_STDLIB_DEPENDS_COLOR_WHEN:-$grep_output_default_color_when}" |
| grep_output_line_prefix="${PYTHON3_FIND_STDLIB_DEPENDS_LINE_PREFIX:-$grep_output_default_line_prefix}" |
| grep_output_context_num="${PYTHON3_FIND_STDLIB_DEPENDS_CONTEXT_NUM:-$grep_output_default_context_num}" |
| |
| |
| # Process command line options |
| |
| while getopts c:hm:n:qvx: OPT; do |
| case $OPT in |
| c) |
| grep_output_color_when="$OPTARG" |
| ;; |
| h) |
| usage |
| exit 0 |
| ;; |
| m) |
| grep_output_max_count="$OPTARG" |
| ;; |
| n) |
| output_name="$OPTARG" |
| ;; |
| q) |
| log_level="$log_level_notice" |
| ;; |
| v) |
| log_level="$log_level_debug" |
| ;; |
| x) |
| grep_output_context_num="$OPTARG" |
| ;; |
| \?) |
| usage |
| exit "$ex_usage" |
| ;; |
| esac |
| done |
| |
| shift $((OPTIND - 1)) |
| |
| |
| # Set up grep output options |
| |
| if can_log_info; then |
| if [ "$grep_output_color_when" = "auto" ]; then |
| if [ -t 1 ]; then |
| grep_output_color_when="always" |
| else |
| grep_output_color_when="never" |
| fi |
| fi |
| |
| if ! can_log_debug && [ "$grep_output_max_count" -gt 0 ]; then |
| grep_output_options="-m $grep_output_max_count" |
| |
| if [ "$grep_output_max_count" -eq 1 ]; then |
| grep_output_description="max 1 match per file" |
| else |
| grep_output_description="max $grep_output_max_count matches per file" |
| fi |
| |
| else |
| grep_output_description="all matches" |
| fi |
| |
| if [ "$grep_output_context_num" -gt 0 ]; then |
| grep_output_options="$grep_output_options -C $grep_output_context_num" |
| |
| if [ "$grep_output_context_num" -eq 1 ]; then |
| grep_output_description="$grep_output_description, 1 line of context" |
| else |
| grep_output_description="$grep_output_description, $grep_output_context_num lines of context" |
| fi |
| fi |
| |
| grep_output_options="$grep_output_options --color=$grep_output_color_when $grep_output_line_prefix" |
| |
| else |
| grep_output_options="-q" |
| fi |
| |
| |
| # Main |
| |
| if [ "$#" -gt 0 ]; then |
| is_first_search=1 |
| |
| if [ "$#" -gt 1 ]; then |
| output_name= |
| fi |
| |
| for path; do |
| if [ -z "$is_first_search" ]; then |
| log_info "====" |
| fi |
| |
| if [ -z "$output_name" ]; then |
| if [ "$path" = "-" ]; then |
| output_name="$stdin_name" |
| else |
| output_name="$path" |
| fi |
| fi |
| |
| search_path "$path" "$output_name" |
| |
| is_first_search= |
| output_name= |
| done |
| |
| else |
| search_path "-" "${output_name:-$stdin_name}" |
| fi |