diagnostics.scm 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix diagnostics)
  19. #:use-module (guix colors)
  20. #:use-module (guix i18n)
  21. #:autoload (guix utils) (<location>)
  22. #:use-module (srfi srfi-26)
  23. #:use-module (ice-9 format)
  24. #:use-module (ice-9 match)
  25. #:export (warning
  26. info
  27. report-error
  28. leave
  29. location->string
  30. guix-warning-port
  31. program-name))
  32. ;;; Commentary:
  33. ;;;
  34. ;;; This module provides the tools to report diagnostics to the user in a
  35. ;;; consistent way: errors, warnings, and notes.
  36. ;;;
  37. ;;; Code:
  38. (define-syntax highlight-argument
  39. (lambda (s)
  40. "Given FMT and ARG, expand ARG to a call that highlights it, provided FMT
  41. is a trivial format string."
  42. (define (trivial-format-string? fmt)
  43. (define len
  44. (string-length fmt))
  45. (let loop ((start 0))
  46. (or (>= (+ 1 start) len)
  47. (let ((tilde (string-index fmt #\~ start)))
  48. (or (not tilde)
  49. (case (string-ref fmt (+ tilde 1))
  50. ((#\a #\A #\%) (loop (+ tilde 2)))
  51. (else #f)))))))
  52. ;; Be conservative: limit format argument highlighting to cases where the
  53. ;; format string contains nothing but ~a escapes. If it contained ~s
  54. ;; escapes, this strategy wouldn't work.
  55. (syntax-case s ()
  56. ((_ "~a~%" arg) ;don't highlight whole messages
  57. #'arg)
  58. ((_ fmt arg)
  59. (trivial-format-string? (syntax->datum #'fmt))
  60. #'(%highlight-argument arg))
  61. ((_ fmt arg)
  62. #'arg))))
  63. (define* (%highlight-argument arg #:optional (port (guix-warning-port)))
  64. "Highlight ARG, a format string argument, if PORT supports colors."
  65. (cond ((string? arg)
  66. ;; If ARG contains white space, don't highlight it, on the grounds
  67. ;; that it may be a complete message in its own, like those produced
  68. ;; by 'guix lint.
  69. (if (string-any char-set:whitespace arg)
  70. arg
  71. (highlight arg port)))
  72. ((symbol? arg)
  73. (highlight (symbol->string arg) port))
  74. (else arg)))
  75. (define-syntax define-diagnostic
  76. (syntax-rules ()
  77. "Create a diagnostic macro (i.e., NAME), which will prepend PREFIX to all
  78. messages."
  79. ((_ name (G_ prefix) colors)
  80. (define-syntax name
  81. (lambda (x)
  82. (syntax-case x ()
  83. ((name location (underscore fmt) args (... ...))
  84. (and (string? (syntax->datum #'fmt))
  85. (free-identifier=? #'underscore #'G_))
  86. #'(begin
  87. (print-diagnostic-prefix prefix location
  88. #:colors colors)
  89. (format (guix-warning-port) (gettext fmt %gettext-domain)
  90. (highlight-argument fmt args) (... ...))))
  91. ((name location (N-underscore singular plural n)
  92. args (... ...))
  93. (and (string? (syntax->datum #'singular))
  94. (string? (syntax->datum #'plural))
  95. (free-identifier=? #'N-underscore #'N_))
  96. #'(begin
  97. (print-diagnostic-prefix prefix location
  98. #:colors colors)
  99. (format (guix-warning-port)
  100. (ngettext singular plural n %gettext-domain)
  101. (highlight-argument singular args) (... ...))))
  102. ((name (underscore fmt) args (... ...))
  103. (free-identifier=? #'underscore #'G_)
  104. #'(name #f (underscore fmt) args (... ...)))
  105. ((name (N-underscore singular plural n)
  106. args (... ...))
  107. (free-identifier=? #'N-underscore #'N_)
  108. #'(name #f (N-underscore singular plural n)
  109. args (... ...)))))))))
  110. ;; XXX: This doesn't work well for right-to-left languages.
  111. ;; TRANSLATORS: The goal is to emit "warning:" followed by a short phrase;
  112. ;; "~a" is a placeholder for that phrase.
  113. (define-diagnostic warning (G_ "warning: ") %warning-color) ;emit a warning
  114. (define-diagnostic info (G_ "") %info-color)
  115. (define-diagnostic report-error (G_ "error: ") %error-color)
  116. (define-syntax-rule (leave args ...)
  117. "Emit an error message and exit."
  118. (begin
  119. (report-error args ...)
  120. (exit 1)))
  121. (define %warning-color (color BOLD MAGENTA))
  122. (define %info-color (color BOLD))
  123. (define %error-color (color BOLD RED))
  124. (define* (print-diagnostic-prefix prefix #:optional location
  125. #:key (colors (color)))
  126. "Print PREFIX as a diagnostic line prefix."
  127. (define color?
  128. (color-output? (guix-warning-port)))
  129. (define location-color
  130. (if color?
  131. (cut colorize-string <> (color BOLD))
  132. identity))
  133. (define prefix-color
  134. (if color?
  135. (lambda (prefix)
  136. (colorize-string prefix colors))
  137. identity))
  138. (let ((prefix (if (string-null? prefix)
  139. prefix
  140. (gettext prefix %gettext-domain))))
  141. (if location
  142. (format (guix-warning-port) "~a: ~a"
  143. (location-color (location->string location))
  144. (prefix-color prefix))
  145. (format (guix-warning-port) "~:[~*~;guix ~a: ~]~a"
  146. (program-name) (program-name)
  147. (prefix-color prefix)))))
  148. (define (location->string loc)
  149. "Return a human-friendly, GNU-standard representation of LOC."
  150. (match loc
  151. (#f (G_ "<unknown location>"))
  152. (($ <location> file line column)
  153. (format #f "~a:~a:~a" file line column))))
  154. (define guix-warning-port
  155. (make-parameter (current-warning-port)))
  156. (define program-name
  157. ;; Name of the command-line program currently executing, or #f.
  158. (make-parameter #f))