emacs.d/elpa/racket-mode-20200328.1644/racket/commands/describe.rkt
2020-03-28 22:53:25 +01:00

127 lines
4.3 KiB
Racket

#lang racket/base
(require racket/contract
racket/format
racket/match
racket/port
(only-in xml xexpr->string)
(only-in "../find.rkt" find-signature)
"../identifier.rkt"
"../scribble.rkt")
(provide type
describe)
(module+ test
(require rackunit))
;;; type
(define/contract (type how str)
(-> how/c string? (or/c #f string?))
(or (and (eq? how 'namespace)
(->identifier 'namespace str type-or-contract))
(->identifier how str identifier->bluebox)
(match (find-signature how str)
[#f #f]
[x (~a x)])))
(define (type-or-contract v) ;any/c -> (or/c #f string?)
(or
;; 1. Try using Typed Racket's REPL simplified type.
(with-handlers ([exn:fail? (λ _ #f)])
(match (with-output-to-string
(λ ()
((current-eval)
(cons '#%top-interaction v))))
[(pregexp "^- : (.*) \\.\\.\\..*\n" (list _ t)) t]
[(pregexp "^- : (.*)\n$" (list _ t)) t]))
;; 2. Try to find a contract.
(with-handlers ([exn:fail? (λ _ #f)])
(parameterize ([error-display-handler (λ _ (void))])
((current-eval)
(cons '#%top-interaction
`(if (has-contract? ,v)
(~a (contract-name (value-contract ,v)))
(error ""))))))))
;;; describe
;; If a symbol has installed documentation, display it.
;;
;; Otherwise, walk the source to find a function definition signature
;; (the argument names may have explanatory value). When using a
;; module->namespace, also look for Typed Racket type or a contract,
;; if any.
(define/contract (describe how str)
(-> (or/c how/c
(cons/c path-string? string?)
(list/c path-string?))
string?
string?)
(match how
[(list (? path-string? path))
(or (path+anchor->html (cons path #f))
"Documentation not found")]
[(cons (? path-string? path) (? string? anchor))
(or (path+anchor->html (cons path anchor))
"Documentation not found")]
[(and (or 'namespace (? path-string?)) how)
(->identifier how str
(λ (stx)
(or (path+anchor->html (binding->path+anchor stx))
(sig-and/or-type how stx))))]))
(define/contract (sig-and/or-type how stx)
(-> how/c identifier? string?)
(define dat (syntax->datum stx))
(define s (match (find-signature how (symbol->string dat))
[#f #f]
[x (~a x)]))
(define t (and (eq? how 'namespace)
(type-or-contract stx)))
(xexpr->string
`(div ()
(h1 () ,(or s (~a dat)))
,(cond [(not (or s t))
`(p ()
(em () ,(if (eq? how 'namespace)
"(Found no documentation, signature, type, or contract.)"
"(Found no documentation or signature.")))]
[t `(pre () ,t)]
[else ""])
(br ()))))
(module+ test
(require rackunit
(only-in xml string->xexpr)
"../syntax.rkt")
;; Check something that is in the namespace resulting from
;; module->namespace on, say, this source file.
(parameterize ([current-namespace (module->namespace (syntax-source #'this-file))])
(check-equal?
;; Convert back to an xexpr because easier to grok test and also
;; older xexpr->string probably used <br /> instead of <br/>.
(string->xexpr (describe 'namespace "describe"))
`(div ()
(h1 () "(describe how str)")
(pre () "(-" ">" " (or/c (or/c (quote namespace) path-string?) (cons/c path-string? string?) (list/c path-string?)) string? string?)")
(br ()))))
;; Check something that is not in the current namespace, but is an
;; identifier in the lexical context of an expanded module form --
;; including imported identifiers -- from the expanded syntax
;; cache.
(define path-str "/path/to/foobar.rkt")
(define code-str (~a '(module foobar racket/base
(define (fun a b c)
(void)))))
;; Get the expanded syntax in our cache
(string->expanded-syntax path-str code-str void)
;; Note that this doesn't find contracts, just sigs.
(check-equal?
(string->xexpr (describe path-str "fun"))
`(div ()
(h1 () "(fun a b c)")
(br ()))))