You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

434 lines
8.3 KiB

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