frisk.scm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. ;;; frisk --- Grok the module interfaces of a body of files
  2. ;; Copyright (C) 2002, 2006, 2010, 2011 Free Software Foundation, Inc.
  3. ;;
  4. ;; This program is free software; you can redistribute it and/or
  5. ;; modify it under the terms of the GNU Lesser General Public License
  6. ;; as published by the Free Software Foundation; either version 3, or
  7. ;; (at your option) any later version.
  8. ;;
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Lesser General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Lesser General Public
  15. ;; License along with this software; see the file COPYING.LESSER. If
  16. ;; not, write to the Free Software Foundation, Inc., 51 Franklin
  17. ;; Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. ;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
  19. ;;; Commentary:
  20. ;; Usage: frisk [options] file ...
  21. ;;
  22. ;; Analyze FILE... module interfaces in aggregate (as a "body"),
  23. ;; and display a summary. Modules that are `define-module'd are
  24. ;; considered "internal" (and those not, "external"). When module X
  25. ;; uses module Y, X is said to be "(a) downstream of" Y, and Y is
  26. ;; "(an) upstream of" X.
  27. ;;
  28. ;; Normally, the summary displays external modules and their internal
  29. ;; downstreams, as this is the usual question asked by a body. There
  30. ;; are several options that modify this output.
  31. ;;
  32. ;; -u, --upstream show upstream edges
  33. ;; -d, --downstream show downstream edges (default)
  34. ;; -i, --internal show internal modules
  35. ;; -x, --external show external modules (default)
  36. ;;
  37. ;; If given both `upstream' and `downstream' options ("frisk -ud"), the
  38. ;; output is formatted: "C MODULE --- UP-LS --- DOWN-LS", where C is
  39. ;; either `i' or `x', and each element of UP-LS and DOWN-LS is (TYPE
  40. ;; MODULE-NAME ...).
  41. ;;
  42. ;; In all other cases, the "C MODULE" occupies its own line, and
  43. ;; subsequent lines list the up- or downstream edges, respectively,
  44. ;; indented by some non-zero amount of whitespace.
  45. ;;
  46. ;; Top-level `use-modules' (or `load' or 'primitive-load') forms in a
  47. ;; file that do not follow a `define-module' result an edge where the
  48. ;; downstream is the "default module", normally `(guile-user)'. This
  49. ;; can be set to another value by using:
  50. ;;
  51. ;; -m, --default-module MOD set MOD as the default module
  52. ;; Usage from a Scheme Program: (use-modules (scripts frisk))
  53. ;;
  54. ;; Module export list:
  55. ;; (frisk . args)
  56. ;; (make-frisker . options) => (lambda (files) ...) [see below]
  57. ;; (mod-up-ls module) => upstream edges
  58. ;; (mod-down-ls module) => downstream edges
  59. ;; (mod-int? module) => is the module internal?
  60. ;; (edge-type edge) => symbol: {regular,autoload,computed}
  61. ;; (edge-up edge) => upstream module
  62. ;; (edge-down edge) => downstream module
  63. ;;
  64. ;; OPTIONS is an alist. Recognized keys are:
  65. ;; default-module
  66. ;;
  67. ;; `make-frisker' returns a procedure that takes a list of files, the
  68. ;; FRISKER. FRISKER returns a closure, REPORT, that takes one of the
  69. ;; keys:
  70. ;; modules -- entire list of modules
  71. ;; internal -- list of internal modules
  72. ;; external -- list of external modules
  73. ;; i-up -- list of modules upstream of internal modules
  74. ;; x-up -- list of modules upstream of external modules
  75. ;; i-down -- list of modules downstream of internal modules
  76. ;; x-down -- list of modules downstream of external modules
  77. ;; edges -- list of edges
  78. ;; Note that `x-up' should always be null, since by (lack of!)
  79. ;; definition, we only know external modules by reference.
  80. ;;
  81. ;; The module and edge objects managed by REPORT can be examined in
  82. ;; detail by using the other (self-explanatory) procedures. Be careful
  83. ;; not to confuse a freshly consed list of symbols, like `(a b c)' with
  84. ;; the module `(a b c)'. If you want to find the module by that name,
  85. ;; try: (cond ((member '(a b c) (REPORT 'modules)) => car)).
  86. ;; TODO: Make "frisk -ud" output less ugly.
  87. ;; Consider default module as internal; add option to invert.
  88. ;; Support `edge-misc' data.
  89. ;;; Code:
  90. (define-module (scripts frisk)
  91. :autoload (ice-9 getopt-long) (getopt-long)
  92. :use-module ((srfi srfi-1) :select (filter remove))
  93. :export (frisk
  94. make-frisker
  95. mod-up-ls mod-down-ls mod-int?
  96. edge-type edge-up edge-down))
  97. (define %include-in-guild-list #f)
  98. (define %summary "Show dependency information for a module.")
  99. (define *default-module* '(guile-user))
  100. (define (grok-proc default-module note-use!)
  101. (lambda (filename)
  102. (let* ((p (open-file filename "r"))
  103. (next (lambda () (read p)))
  104. (ferret (lambda (use) ;;; handle "((foo bar) :select ...)"
  105. (let ((maybe (car use)))
  106. (if (list? maybe)
  107. maybe
  108. use))))
  109. (curmod #f))
  110. (let loop ((form (next)))
  111. (cond ((eof-object? form))
  112. ((not (list? form)) (loop (next)))
  113. (else (case (car form)
  114. ((define-module)
  115. (let ((module (cadr form)))
  116. (set! curmod module)
  117. (note-use! 'def module #f)
  118. (let loop ((ls form))
  119. (or (null? ls)
  120. (case (car ls)
  121. ((#:use-module :use-module)
  122. (note-use! 'regular module (ferret (cadr ls)))
  123. (loop (cddr ls)))
  124. ((#:autoload :autoload)
  125. (note-use! 'autoload module (cadr ls))
  126. (loop (cdddr ls)))
  127. (else (loop (cdr ls))))))))
  128. ((use-modules)
  129. (for-each (lambda (use)
  130. (note-use! 'regular
  131. (or curmod default-module)
  132. (ferret use)))
  133. (cdr form)))
  134. ((load primitive-load)
  135. (note-use! 'computed
  136. (or curmod default-module)
  137. (let ((file (cadr form)))
  138. (if (string? file)
  139. file
  140. (format #f "[computed in ~A]"
  141. filename))))))
  142. (loop (next))))))))
  143. (define up-ls (make-object-property)) ; list
  144. (define dn-ls (make-object-property)) ; list
  145. (define int? (make-object-property)) ; defined via `define-module'
  146. (define mod-up-ls up-ls)
  147. (define mod-down-ls dn-ls)
  148. (define mod-int? int?)
  149. (define (i-or-x module)
  150. (if (int? module) 'i 'x))
  151. (define edge-type (make-object-property)) ; symbol
  152. (define (make-edge type up down)
  153. (let ((new (cons up down)))
  154. (set! (edge-type new) type)
  155. new))
  156. (define edge-up car)
  157. (define edge-down cdr)
  158. (define (up-ls+! m new) (set! (up-ls m) (cons new (up-ls m))))
  159. (define (dn-ls+! m new) (set! (dn-ls m) (cons new (dn-ls m))))
  160. (define (make-body alist)
  161. (lambda (key)
  162. (assq-ref alist key)))
  163. (define (scan default-module files)
  164. (let* ((modules (list))
  165. (edges (list))
  166. (intern (lambda (module)
  167. (cond ((member module modules) => car)
  168. (else (set! (up-ls module) (list))
  169. (set! (dn-ls module) (list))
  170. (set! modules (cons module modules))
  171. module))))
  172. (grok (grok-proc default-module
  173. (lambda (type d u)
  174. (let ((d (intern d)))
  175. (if (eq? type 'def)
  176. (set! (int? d) #t)
  177. (let* ((u (intern u))
  178. (edge (make-edge type u d)))
  179. (set! edges (cons edge edges))
  180. (up-ls+! d edge)
  181. (dn-ls+! u edge))))))))
  182. (for-each grok files)
  183. (make-body
  184. `((modules . ,modules)
  185. (internal . ,(filter int? modules))
  186. (external . ,(remove int? modules))
  187. (i-up . ,(filter int? (map edge-down edges)))
  188. (x-up . ,(remove int? (map edge-down edges)))
  189. (i-down . ,(filter int? (map edge-up edges)))
  190. (x-down . ,(remove int? (map edge-up edges)))
  191. (edges . ,edges)))))
  192. (define (make-frisker . options)
  193. (let ((default-module (or (assq-ref options 'default-module)
  194. *default-module*)))
  195. (lambda (files)
  196. (scan default-module files))))
  197. (define (dump-updown modules)
  198. (for-each (lambda (m)
  199. (format #t "~A ~A --- ~A --- ~A\n"
  200. (i-or-x m) m
  201. (map (lambda (edge)
  202. (cons (edge-type edge)
  203. (edge-up edge)))
  204. (up-ls m))
  205. (map (lambda (edge)
  206. (cons (edge-type edge)
  207. (edge-down edge)))
  208. (dn-ls m))))
  209. modules))
  210. (define (dump-up modules)
  211. (for-each (lambda (m)
  212. (format #t "~A ~A\n" (i-or-x m) m)
  213. (for-each (lambda (edge)
  214. (format #t "\t\t\t ~A\t~A\n"
  215. (edge-type edge) (edge-up edge)))
  216. (up-ls m)))
  217. modules))
  218. (define (dump-down modules)
  219. (for-each (lambda (m)
  220. (format #t "~A ~A\n" (i-or-x m) m)
  221. (for-each (lambda (edge)
  222. (format #t "\t\t\t ~A\t~A\n"
  223. (edge-type edge) (edge-down edge)))
  224. (dn-ls m)))
  225. modules))
  226. (define (frisk . args)
  227. (let* ((parsed-opts (getopt-long
  228. (cons "frisk" args) ;;; kludge
  229. '((upstream (single-char #\u))
  230. (downstream (single-char #\d))
  231. (internal (single-char #\i))
  232. (external (single-char #\x))
  233. (default-module
  234. (single-char #\m)
  235. (value #t)))))
  236. (=u (option-ref parsed-opts 'upstream #f))
  237. (=d (option-ref parsed-opts 'downstream #f))
  238. (=i (option-ref parsed-opts 'internal #f))
  239. (=x (option-ref parsed-opts 'external #f))
  240. (files (option-ref parsed-opts '() (list)))
  241. (report ((make-frisker
  242. `(default-module
  243. . ,(option-ref parsed-opts 'default-module
  244. *default-module*)))
  245. files))
  246. (modules (report 'modules))
  247. (internal (report 'internal))
  248. (external (report 'external))
  249. (edges (report 'edges)))
  250. (format #t "~A ~A, ~A ~A (~A ~A, ~A ~A), ~A ~A\n\n"
  251. (length files) "files"
  252. (length modules) "modules"
  253. (length internal) "internal"
  254. (length external) "external"
  255. (length edges) "edges")
  256. ((cond ((and =u =d) dump-updown)
  257. (=u dump-up)
  258. (else dump-down))
  259. (cond ((and =i =x) modules)
  260. (=i internal)
  261. (else external)))))
  262. (define main frisk)
  263. ;;; frisk ends here