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.

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