Spaces:
Sleeping
Sleeping
#!/bin/sh | |
# aside from this initial boilerplate, this is actually -*- scheme -*- code | |
main='(module-ref (resolve-module '\''(scripts frisk)) '\'main')' | |
exec ${GUILE-guile} -l $0 -c "(apply $main (cdr (command-line)))" "$@" | |
!# | |
;;; frisk --- Grok the module interfaces of a body of files | |
;; Copyright (C) 2002, 2006 Free Software Foundation, Inc. | |
;; | |
;; This program is free software; you can redistribute it and/or | |
;; modify it under the terms of the GNU General Public License as | |
;; published by the Free Software Foundation; either version 2, or | |
;; (at your option) any later version. | |
;; | |
;; This program is distributed in the hope that it will be useful, | |
;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
;; General Public License for more details. | |
;; | |
;; You should have received a copy of the GNU General Public License | |
;; along with this software; see the file COPYING. If not, write to | |
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
;; Boston, MA 02110-1301 USA | |
;;; Author: Thien-Thi Nguyen <ttn@gnu.org> | |
;;; Commentary: | |
;; Usage: frisk [options] file ... | |
;; | |
;; Analyze FILE... module interfaces in aggregate (as a "body"), | |
;; and display a summary. Modules that are `define-module'd are | |
;; considered "internal" (and those not, "external"). When module X | |
;; uses module Y, X is said to be "(a) downstream of" Y, and Y is | |
;; "(an) upstream of" X. | |
;; | |
;; Normally, the summary displays external modules and their internal | |
;; downstreams, as this is the usual question asked by a body. There | |
;; are several options that modify this output. | |
;; | |
;; -u, --upstream show upstream edges | |
;; -d, --downstream show downstream edges (default) | |
;; -i, --internal show internal modules | |
;; -x, --external show external modules (default) | |
;; | |
;; If given both `upstream' and `downstream' options ("frisk -ud"), the | |
;; output is formatted: "C MODULE --- UP-LS --- DOWN-LS", where C is | |
;; either `i' or `x', and each element of UP-LS and DOWN-LS is (TYPE | |
;; MODULE-NAME ...). | |
;; | |
;; In all other cases, the "C MODULE" occupies its own line, and | |
;; subsequent lines list the up- or downstream edges, respectively, | |
;; indented by some non-zero amount of whitespace. | |
;; | |
;; Top-level `use-modules' (or `load' or 'primitive-load') forms in a | |
;; file that do not follow a `define-module' result an edge where the | |
;; downstream is the "default module", normally `(guile-user)'. This | |
;; can be set to another value by using: | |
;; | |
;; -m, --default-module MOD set MOD as the default module | |
;; Usage from a Scheme Program: (use-modules (scripts frisk)) | |
;; | |
;; Module export list: | |
;; (frisk . args) | |
;; (make-frisker . options) => (lambda (files) ...) [see below] | |
;; (mod-up-ls module) => upstream edges | |
;; (mod-down-ls module) => downstream edges | |
;; (mod-int? module) => is the module internal? | |
;; (edge-type edge) => symbol: {regular,autoload,computed} | |
;; (edge-up edge) => upstream module | |
;; (edge-down edge) => downstream module | |
;; | |
;; OPTIONS is an alist. Recognized keys are: | |
;; default-module | |
;; | |
;; `make-frisker' returns a procedure that takes a list of files, the | |
;; FRISKER. FRISKER returns a closure, REPORT, that takes one of the | |
;; keys: | |
;; modules -- entire list of modules | |
;; internal -- list of internal modules | |
;; external -- list of external modules | |
;; i-up -- list of modules upstream of internal modules | |
;; x-up -- list of modules upstream of external modules | |
;; i-down -- list of modules downstream of internal modules | |
;; x-down -- list of modules downstream of external modules | |
;; edges -- list of edges | |
;; Note that `x-up' should always be null, since by (lack of!) | |
;; definition, we only know external modules by reference. | |
;; | |
;; The module and edge objects managed by REPORT can be examined in | |
;; detail by using the other (self-explanatory) procedures. Be careful | |
;; not to confuse a freshly consed list of symbols, like `(a b c)' with | |
;; the module `(a b c)'. If you want to find the module by that name, | |
;; try: (cond ((member '(a b c) (REPORT 'modules)) => car)). | |
;; TODO: Make "frisk -ud" output less ugly. | |
;; Consider default module as internal; add option to invert. | |
;; Support `edge-misc' data. | |
;;; Code: | |
(define-module (scripts frisk) | |
:autoload (ice-9 getopt-long) (getopt-long) | |
:use-module ((srfi srfi-1) :select (filter remove)) | |
:export (frisk | |
make-frisker | |
mod-up-ls mod-down-ls mod-int? | |
edge-type edge-up edge-down)) | |
(define *default-module* '(guile-user)) | |
(define (grok-proc default-module note-use!) | |
(lambda (filename) | |
(let* ((p (open-file filename "r")) | |
(next (lambda () (read p))) | |
(ferret (lambda (use) ;;; handle "((foo bar) :select ...)" | |
(let ((maybe (car use))) | |
(if (list? maybe) | |
maybe | |
use)))) | |
(curmod #f)) | |
(let loop ((form (next))) | |
(cond ((eof-object? form)) | |
((not (list? form)) (loop (next))) | |
(else (case (car form) | |
((define-module) | |
(let ((module (cadr form))) | |
(set! curmod module) | |
(note-use! 'def module #f) | |
(let loop ((ls form)) | |
(or (null? ls) | |
(case (car ls) | |
((:use-module) | |
(note-use! 'regular module (ferret (cadr ls))) | |
(loop (cddr ls))) | |
((:autoload) | |
(note-use! 'autoload module (cadr ls)) | |
(loop (cdddr ls))) | |
(else (loop (cdr ls)))))))) | |
((use-modules) | |
(for-each (lambda (use) | |
(note-use! 'regular | |
(or curmod default-module) | |
(ferret use))) | |
(cdr form))) | |
((load primitive-load) | |
(note-use! 'computed | |
(or curmod default-module) | |
(let ((file (cadr form))) | |
(if (string? file) | |
file | |
(format #f "[computed in ~A]" | |
filename)))))) | |
(loop (next)))))))) | |
(define up-ls (make-object-property)) ; list | |
(define dn-ls (make-object-property)) ; list | |
(define int? (make-object-property)) ; defined via `define-module' | |
(define mod-up-ls up-ls) | |
(define mod-down-ls dn-ls) | |
(define mod-int? int?) | |
(define (i-or-x module) | |
(if (int? module) 'i 'x)) | |
(define edge-type (make-object-property)) ; symbol | |
(define (make-edge type up down) | |
(let ((new (cons up down))) | |
(set! (edge-type new) type) | |
new)) | |
(define edge-up car) | |
(define edge-down cdr) | |
(define (up-ls+! m new) (set! (up-ls m) (cons new (up-ls m)))) | |
(define (dn-ls+! m new) (set! (dn-ls m) (cons new (dn-ls m)))) | |
(define (make-body alist) | |
(lambda (key) | |
(assq-ref alist key))) | |
(define (scan default-module files) | |
(let* ((modules (list)) | |
(edges (list)) | |
(intern (lambda (module) | |
(cond ((member module modules) => car) | |
(else (set! (up-ls module) (list)) | |
(set! (dn-ls module) (list)) | |
(set! modules (cons module modules)) | |
module)))) | |
(grok (grok-proc default-module | |
(lambda (type d u) | |
(let ((d (intern d))) | |
(if (eq? type 'def) | |
(set! (int? d) #t) | |
(let* ((u (intern u)) | |
(edge (make-edge type u d))) | |
(set! edges (cons edge edges)) | |
(up-ls+! d edge) | |
(dn-ls+! u edge)))))))) | |
(for-each grok files) | |
(make-body | |
`((modules . ,modules) | |
(internal . ,(filter int? modules)) | |
(external . ,(remove int? modules)) | |
(i-up . ,(filter int? (map edge-down edges))) | |
(x-up . ,(remove int? (map edge-down edges))) | |
(i-down . ,(filter int? (map edge-up edges))) | |
(x-down . ,(remove int? (map edge-up edges))) | |
(edges . ,edges))))) | |
(define (make-frisker . options) | |
(let ((default-module (or (assq-ref options 'default-module) | |
*default-module*))) | |
(lambda (files) | |
(scan default-module files)))) | |
(define (dump-updown modules) | |
(for-each (lambda (m) | |
(format #t "~A ~A --- ~A --- ~A\n" | |
(i-or-x m) m | |
(map (lambda (edge) | |
(cons (edge-type edge) | |
(edge-up edge))) | |
(up-ls m)) | |
(map (lambda (edge) | |
(cons (edge-type edge) | |
(edge-down edge))) | |
(dn-ls m)))) | |
modules)) | |
(define (dump-up modules) | |
(for-each (lambda (m) | |
(format #t "~A ~A\n" (i-or-x m) m) | |
(for-each (lambda (edge) | |
(format #t "\t\t\t ~A\t~A\n" | |
(edge-type edge) (edge-up edge))) | |
(up-ls m))) | |
modules)) | |
(define (dump-down modules) | |
(for-each (lambda (m) | |
(format #t "~A ~A\n" (i-or-x m) m) | |
(for-each (lambda (edge) | |
(format #t "\t\t\t ~A\t~A\n" | |
(edge-type edge) (edge-down edge))) | |
(dn-ls m))) | |
modules)) | |
(define (frisk . args) | |
(let* ((parsed-opts (getopt-long | |
(cons "frisk" args) ;;; kludge | |
'((upstream (single-char #\u)) | |
(downstream (single-char #\d)) | |
(internal (single-char #\i)) | |
(external (single-char #\x)) | |
(default-module | |
(single-char #\m) | |
(value #t))))) | |
(=u (option-ref parsed-opts 'upstream #f)) | |
(=d (option-ref parsed-opts 'downstream #f)) | |
(=i (option-ref parsed-opts 'internal #f)) | |
(=x (option-ref parsed-opts 'external #f)) | |
(files (option-ref parsed-opts '() (list))) | |
(report ((make-frisker | |
`(default-module | |
. ,(option-ref parsed-opts 'default-module | |
*default-module*))) | |
files)) | |
(modules (report 'modules)) | |
(internal (report 'internal)) | |
(external (report 'external)) | |
(edges (report 'edges))) | |
(format #t "~A ~A, ~A ~A (~A ~A, ~A ~A), ~A ~A\n\n" | |
(length files) "files" | |
(length modules) "modules" | |
(length internal) "internal" | |
(length external) "external" | |
(length edges) "edges") | |
((cond ((and =u =d) dump-updown) | |
(=u dump-up) | |
(else dump-down)) | |
(cond ((and =i =x) modules) | |
(=i internal) | |
(else external))))) | |
(define main frisk) | |
;;; frisk ends here | |