b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | #!/bin/sh |
| 2 | |
| 3 | # Packages data |
| 4 | # |
| 5 | # Notes: |
| 6 | # * python3-codecs: Also contains codecs for CJK encodings but we don't |
| 7 | # have a good way to check for uses |
| 8 | # * python3-openssl: Don't include hashlib as it supports several |
| 9 | # standard algorithms without requiring OpenSSL |
| 10 | |
| 11 | packages=" |
| 12 | python3-asyncio: asyncio |
| 13 | python3-cgi: cgi |
| 14 | python3-cgitb: cgitb |
| 15 | python3-codecs: unicodedata |
| 16 | python3-ctypes: ctypes |
| 17 | python3-dbm: dbm dbm.dumb dbm.gnu dbm.ndbm |
| 18 | python3-decimal: decimal |
| 19 | python3-distutils: distutils |
| 20 | python3-email: email |
| 21 | python3-logging: logging |
| 22 | python3-lzma: lzma |
| 23 | python3-multiprocessing: multiprocessing |
| 24 | python3-ncurses: ncurses |
| 25 | python3-openssl: ssl |
| 26 | python3-pydoc: doctest pydoc |
| 27 | python3-readline: readline |
| 28 | python3-sqlite3: sqlite3 |
| 29 | python3-unittest: unittest |
| 30 | python3-urllib: urllib |
| 31 | python3-uuid: uuid |
| 32 | python3-xml: xml xmlrpc |
| 33 | " |
| 34 | |
| 35 | |
| 36 | # Constants |
| 37 | |
| 38 | stdin_name="<stdin>" |
| 39 | grep_dir_filters=" |
| 40 | -Ir |
| 41 | --include=*.py |
| 42 | --exclude=setup.py |
| 43 | --exclude=test_*.py |
| 44 | --exclude=*_test.py |
| 45 | --exclude-dir=test |
| 46 | --exclude-dir=tests |
| 47 | --exclude-dir=ipkg-* |
| 48 | --exclude-dir=.pkgdir |
| 49 | " |
| 50 | |
| 51 | log_level_notice=5 |
| 52 | log_level_info=6 |
| 53 | log_level_debug=7 |
| 54 | |
| 55 | # /usr/include/sysexits.h |
| 56 | ex_usage=64 |
| 57 | ex_noinput=66 |
| 58 | ex_unavailable=69 |
| 59 | ex_software=70 |
| 60 | |
| 61 | newline=" |
| 62 | " |
| 63 | oifs="$IFS" |
| 64 | |
| 65 | |
| 66 | # Defaults |
| 67 | |
| 68 | grep_output_default_max_count=3 |
| 69 | grep_output_default_color_when="auto" |
| 70 | grep_output_default_line_prefix="-HnT --label=$stdin_name" |
| 71 | grep_output_default_context_num=1 |
| 72 | |
| 73 | log_level_default="$log_level_info" |
| 74 | |
| 75 | |
| 76 | # Global variables |
| 77 | |
| 78 | log_level= |
| 79 | grep= |
| 80 | grep_output_options= |
| 81 | grep_output_description= |
| 82 | stdin= |
| 83 | output_name= |
| 84 | is_first_search= |
| 85 | |
| 86 | |
| 87 | # Logging |
| 88 | |
| 89 | log() { |
| 90 | printf '%s\n' "$*" |
| 91 | } |
| 92 | |
| 93 | can_log_notice() { |
| 94 | [ "$log_level" -ge "$log_level_notice" ] |
| 95 | } |
| 96 | |
| 97 | can_log_info() { |
| 98 | [ "$log_level" -ge "$log_level_info" ] |
| 99 | } |
| 100 | |
| 101 | can_log_debug() { |
| 102 | [ "$log_level" -ge "$log_level_debug" ] |
| 103 | } |
| 104 | |
| 105 | log_notice() { |
| 106 | if can_log_notice; then |
| 107 | log "$@" |
| 108 | fi |
| 109 | } |
| 110 | |
| 111 | log_info() { |
| 112 | if can_log_info; then |
| 113 | log "$@" |
| 114 | fi |
| 115 | } |
| 116 | |
| 117 | log_debug() { |
| 118 | if can_log_debug; then |
| 119 | log "$@" |
| 120 | fi |
| 121 | } |
| 122 | |
| 123 | log_error() { |
| 124 | printf '%s\n' "Error: $*" >&2 |
| 125 | } |
| 126 | |
| 127 | |
| 128 | # Usage |
| 129 | |
| 130 | usage() { |
| 131 | cat <<- EOF |
| 132 | Usage: ${0##*/} [OPTION]... [FILE]... |
| 133 | Search for imports of certain Python standard libraries in each FILE, |
| 134 | generate a list of suggested package dependencies. |
| 135 | |
| 136 | With no FILE, or when FILE is -, read standard input. |
| 137 | |
| 138 | Options: |
| 139 | -c WHEN use color in output; |
| 140 | WHEN is 'always', 'never', or 'auto' (default: '$grep_output_default_color_when') |
| 141 | -h display this help text and exit |
| 142 | -m NUM show max NUM matches per package per file (default: $grep_output_default_max_count); |
| 143 | use 0 to show all matches |
| 144 | -n NAME when one or no FILE is given, use NAME instead of FILE in |
| 145 | displayed information |
| 146 | -q show suggested dependencies only |
| 147 | -v show verbose output (also show all matches) |
| 148 | -x NUM show NUM lines of context (default: $grep_output_default_context_num) |
| 149 | |
| 150 | EOF |
| 151 | } |
| 152 | |
| 153 | |
| 154 | # Imports search |
| 155 | |
| 156 | get_package_modules() { |
| 157 | local line="$1" |
| 158 | local field_num=0 |
| 159 | local IFS=: |
| 160 | |
| 161 | for field in $line; do |
| 162 | # strip leading and trailing whitespace |
| 163 | field="${field#"${field%%[! ]*}"}" |
| 164 | field="${field%"${field##*[! ]}"}" |
| 165 | |
| 166 | # set variables in search_path() |
| 167 | if [ "$field_num" -eq 0 ]; then |
| 168 | package="$field" |
| 169 | field_num=1 |
| 170 | elif [ "$field_num" -eq 1 ]; then |
| 171 | modules="$field" |
| 172 | field_num=2 |
| 173 | else |
| 174 | field_num=3 |
| 175 | fi |
| 176 | done |
| 177 | |
| 178 | if [ "$field_num" -ne 2 ] || [ -z "$package" ] || [ -z "$modules" ]; then |
| 179 | log_error "invalid package data \"$line\"" |
| 180 | exit "$ex_software" |
| 181 | fi |
| 182 | } |
| 183 | |
| 184 | search_path_for_modules() { |
| 185 | local path="$1" |
| 186 | local path_is_dir="$2" |
| 187 | local path_is_stdin="$3" |
| 188 | local package="$4" |
| 189 | local modules="$5" |
| 190 | local modules_regex |
| 191 | local regex |
| 192 | local remove_dir_prefix |
| 193 | local grep_results |
| 194 | local IFS="$oifs" |
| 195 | |
| 196 | log_debug " Looking for modules in $package ($modules)" |
| 197 | |
| 198 | modules_regex=$(printf '%s' "$modules" | sed -e 's/\./\\./g' -e 's/\s\+/|/g') |
| 199 | regex="\b(import\s+($modules_regex)|from\s+($modules_regex)\S*\s+import)\b" |
| 200 | |
| 201 | if [ -n "$path_is_dir" ]; then |
| 202 | remove_dir_prefix="s|^\(\(\x1b[[0-9;]*[mK]\)*\)$path|\1|" |
| 203 | grep_results=$($grep $grep_output_options $grep_dir_filters -E "$regex" "$path") |
| 204 | |
| 205 | elif [ -n "$path_is_stdin" ]; then |
| 206 | grep_results=$(printf '%s\n' "$stdin" | $grep $grep_output_options -E "$regex") |
| 207 | |
| 208 | else |
| 209 | grep_results=$($grep $grep_output_options -E "$regex" "$path") |
| 210 | fi |
| 211 | |
| 212 | if [ "$?" -ne 0 ]; then |
| 213 | log_debug " No imports found" |
| 214 | log_debug "" |
| 215 | return 0 |
| 216 | fi |
| 217 | |
| 218 | log_info " Found imports for: $modules" |
| 219 | |
| 220 | if can_log_info; then |
| 221 | printf '%s\n' "$grep_results" | sed -e "$remove_dir_prefix" -e "s/^/ /" |
| 222 | fi |
| 223 | |
| 224 | log_info "" |
| 225 | |
| 226 | # set variable in search_path() |
| 227 | suggested="$suggested +$package" |
| 228 | } |
| 229 | |
| 230 | search_path() { |
| 231 | local path="$1" |
| 232 | local name="$2" |
| 233 | local path_is_dir |
| 234 | local path_is_stdin |
| 235 | local package |
| 236 | local modules |
| 237 | local suggested |
| 238 | local IFS="$newline" |
| 239 | |
| 240 | if [ "$path" = "-" ]; then |
| 241 | path_is_stdin=1 |
| 242 | |
| 243 | else |
| 244 | if ! [ -e "$path" ]; then |
| 245 | log_error "$path does not exist" |
| 246 | exit "$ex_noinput" |
| 247 | fi |
| 248 | |
| 249 | if [ -d "$path" ]; then |
| 250 | path="${path%/}/" |
| 251 | path_is_dir=1 |
| 252 | fi |
| 253 | fi |
| 254 | |
| 255 | log_info "" |
| 256 | log_info "Searching $name (showing $grep_output_description):" |
| 257 | log_info "" |
| 258 | |
| 259 | if [ -n "$path_is_stdin" ]; then |
| 260 | stdin="$(cat)" |
| 261 | fi |
| 262 | |
| 263 | for line in $packages; do |
| 264 | # strip leading whitespace |
| 265 | line="${line#"${line%%[! ]*}"}" |
| 266 | |
| 267 | # skip blank lines or comments (beginning with #) |
| 268 | if [ -z "$line" ] || [ "$line" != "${line###}" ]; then |
| 269 | continue |
| 270 | fi |
| 271 | |
| 272 | package= |
| 273 | modules= |
| 274 | |
| 275 | get_package_modules "$line" |
| 276 | search_path_for_modules "$path" "$path_is_dir" "$path_is_stdin" "$package" "$modules" |
| 277 | done |
| 278 | |
| 279 | log_notice "Suggested dependencies for $name:" |
| 280 | |
| 281 | if [ -z "$suggested" ]; then |
| 282 | suggested="(none)" |
| 283 | fi |
| 284 | IFS="$oifs" |
| 285 | for package in $suggested; do |
| 286 | log_notice " $package" |
| 287 | done |
| 288 | |
| 289 | log_notice "" |
| 290 | } |
| 291 | |
| 292 | |
| 293 | # Find GNU grep |
| 294 | |
| 295 | if ggrep --version 2>&1 | grep -q GNU; then |
| 296 | grep="ggrep" |
| 297 | elif grep --version 2>&1 | grep -q GNU; then |
| 298 | grep="grep" |
| 299 | else |
| 300 | log_error "cannot find GNU grep" |
| 301 | exit "$ex_unavailable" |
| 302 | fi |
| 303 | |
| 304 | |
| 305 | # Process environment variables |
| 306 | |
| 307 | case $PYTHON3_FIND_STDLIB_DEPENDS_LOG_LEVEL in |
| 308 | notice) |
| 309 | log_level="$log_level_notice" |
| 310 | ;; |
| 311 | info) |
| 312 | log_level="$log_level_info" |
| 313 | ;; |
| 314 | debug) |
| 315 | log_level="$log_level_debug" |
| 316 | ;; |
| 317 | *) |
| 318 | log_level="$log_level_default" |
| 319 | ;; |
| 320 | esac |
| 321 | |
| 322 | grep_output_max_count="${PYTHON3_FIND_STDLIB_DEPENDS_MAX_COUNT:-$grep_output_default_max_count}" |
| 323 | grep_output_color_when="${PYTHON3_FIND_STDLIB_DEPENDS_COLOR_WHEN:-$grep_output_default_color_when}" |
| 324 | grep_output_line_prefix="${PYTHON3_FIND_STDLIB_DEPENDS_LINE_PREFIX:-$grep_output_default_line_prefix}" |
| 325 | grep_output_context_num="${PYTHON3_FIND_STDLIB_DEPENDS_CONTEXT_NUM:-$grep_output_default_context_num}" |
| 326 | |
| 327 | |
| 328 | # Process command line options |
| 329 | |
| 330 | while getopts c:hm:n:qvx: OPT; do |
| 331 | case $OPT in |
| 332 | c) |
| 333 | grep_output_color_when="$OPTARG" |
| 334 | ;; |
| 335 | h) |
| 336 | usage |
| 337 | exit 0 |
| 338 | ;; |
| 339 | m) |
| 340 | grep_output_max_count="$OPTARG" |
| 341 | ;; |
| 342 | n) |
| 343 | output_name="$OPTARG" |
| 344 | ;; |
| 345 | q) |
| 346 | log_level="$log_level_notice" |
| 347 | ;; |
| 348 | v) |
| 349 | log_level="$log_level_debug" |
| 350 | ;; |
| 351 | x) |
| 352 | grep_output_context_num="$OPTARG" |
| 353 | ;; |
| 354 | \?) |
| 355 | usage |
| 356 | exit "$ex_usage" |
| 357 | ;; |
| 358 | esac |
| 359 | done |
| 360 | |
| 361 | shift $((OPTIND - 1)) |
| 362 | |
| 363 | |
| 364 | # Set up grep output options |
| 365 | |
| 366 | if can_log_info; then |
| 367 | if [ "$grep_output_color_when" = "auto" ]; then |
| 368 | if [ -t 1 ]; then |
| 369 | grep_output_color_when="always" |
| 370 | else |
| 371 | grep_output_color_when="never" |
| 372 | fi |
| 373 | fi |
| 374 | |
| 375 | if ! can_log_debug && [ "$grep_output_max_count" -gt 0 ]; then |
| 376 | grep_output_options="-m $grep_output_max_count" |
| 377 | |
| 378 | if [ "$grep_output_max_count" -eq 1 ]; then |
| 379 | grep_output_description="max 1 match per file" |
| 380 | else |
| 381 | grep_output_description="max $grep_output_max_count matches per file" |
| 382 | fi |
| 383 | |
| 384 | else |
| 385 | grep_output_description="all matches" |
| 386 | fi |
| 387 | |
| 388 | if [ "$grep_output_context_num" -gt 0 ]; then |
| 389 | grep_output_options="$grep_output_options -C $grep_output_context_num" |
| 390 | |
| 391 | if [ "$grep_output_context_num" -eq 1 ]; then |
| 392 | grep_output_description="$grep_output_description, 1 line of context" |
| 393 | else |
| 394 | grep_output_description="$grep_output_description, $grep_output_context_num lines of context" |
| 395 | fi |
| 396 | fi |
| 397 | |
| 398 | grep_output_options="$grep_output_options --color=$grep_output_color_when $grep_output_line_prefix" |
| 399 | |
| 400 | else |
| 401 | grep_output_options="-q" |
| 402 | fi |
| 403 | |
| 404 | |
| 405 | # Main |
| 406 | |
| 407 | if [ "$#" -gt 0 ]; then |
| 408 | is_first_search=1 |
| 409 | |
| 410 | if [ "$#" -gt 1 ]; then |
| 411 | output_name= |
| 412 | fi |
| 413 | |
| 414 | for path; do |
| 415 | if [ -z "$is_first_search" ]; then |
| 416 | log_info "====" |
| 417 | fi |
| 418 | |
| 419 | if [ -z "$output_name" ]; then |
| 420 | if [ "$path" = "-" ]; then |
| 421 | output_name="$stdin_name" |
| 422 | else |
| 423 | output_name="$path" |
| 424 | fi |
| 425 | fi |
| 426 | |
| 427 | search_path "$path" "$output_name" |
| 428 | |
| 429 | is_first_search= |
| 430 | output_name= |
| 431 | done |
| 432 | |
| 433 | else |
| 434 | search_path "-" "${output_name:-$stdin_name}" |
| 435 | fi |