colors.scm 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013, 2014 Free Software Foundation, Inc.
  3. ;;; Copyright © 2018 Sahithi Yarlagadda <sahi@swecha.net>
  4. ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
  5. ;;; Copyright © 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (guix colors)
  22. #:use-module (guix memoization)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (srfi srfi-9)
  25. #:use-module (srfi srfi-9 gnu)
  26. #:use-module (ice-9 match)
  27. #:use-module (ice-9 regex)
  28. #:export (color
  29. color?
  30. colorize-string
  31. highlight
  32. dim
  33. color-rules
  34. color-output?
  35. isatty?*))
  36. ;;; Commentary:
  37. ;;;
  38. ;;; This module provides tools to produce colored output using ANSI escapes.
  39. ;;;
  40. ;;; Code:
  41. ;; Record type for "colors", which are actually lists of color attributes.
  42. (define-record-type <color>
  43. (make-color symbols ansi)
  44. color?
  45. (symbols color-symbols)
  46. (ansi color-ansi))
  47. (define (print-color color port)
  48. (format port "#<color ~a>"
  49. (string-join (map symbol->string
  50. (color-symbols color)))))
  51. (set-record-type-printer! <color> print-color)
  52. (define-syntax define-color-table
  53. (syntax-rules ()
  54. "Define NAME as a macro that builds a list of color attributes."
  55. ((_ name (color escape) ...)
  56. (begin
  57. (define-syntax color-codes
  58. (syntax-rules (color ...)
  59. ((_)
  60. '())
  61. ((_ color rest (... ...))
  62. `(escape ,@(color-codes rest (... ...))))
  63. ...))
  64. (define-syntax-rule (name colors (... ...))
  65. "Return a list of color attributes that can be passed to
  66. 'colorize-string'."
  67. (make-color '(colors (... ...))
  68. (color-codes->ansi (color-codes colors (... ...)))))))))
  69. (define-color-table color
  70. (CLEAR "0")
  71. (RESET "0")
  72. (BOLD "1")
  73. (DARK "2")
  74. (UNDERLINE "4")
  75. (UNDERSCORE "4")
  76. (BLINK "5")
  77. (REVERSE "6")
  78. (CONCEALED "8")
  79. (BLACK "30")
  80. (RED "31")
  81. (GREEN "32")
  82. (YELLOW "33")
  83. (BLUE "34")
  84. (MAGENTA "35")
  85. (CYAN "36")
  86. (WHITE "37")
  87. (ON-BLACK "40")
  88. (ON-RED "41")
  89. (ON-GREEN "42")
  90. (ON-YELLOW "43")
  91. (ON-BLUE "44")
  92. (ON-MAGENTA "45")
  93. (ON-CYAN "46")
  94. (ON-WHITE "47"))
  95. (define (color-codes->ansi codes)
  96. "Convert CODES, a list of color attribute codes, to a ANSI escape string."
  97. (match codes
  98. (()
  99. "")
  100. (_
  101. (string-append (string #\esc #\[)
  102. (string-join codes ";" 'infix)
  103. "m"))))
  104. (define %reset
  105. (color RESET))
  106. (define (colorize-string str color)
  107. "Return a copy of STR colorized using ANSI escape sequences according to
  108. COLOR. At the end of the returned string, the color attributes are reset such
  109. that subsequent output will not have any colors in effect."
  110. (string-append (color-ansi color)
  111. str
  112. (color-ansi %reset)))
  113. (define isatty?*
  114. (mlambdaq (port)
  115. "Return true if PORT is a tty. Memoize the result."
  116. (isatty? port)))
  117. (define (color-output? port)
  118. "Return true if we should write colored output to PORT."
  119. (and (not (getenv "NO_COLOR"))
  120. (isatty?* port)))
  121. (define (coloring-procedure color)
  122. "Return a procedure that applies COLOR to the given string."
  123. (lambda* (str #:optional (port (current-output-port)))
  124. "Return STR with extra ANSI color attributes if PORT supports it."
  125. (if (color-output? port)
  126. (colorize-string str color)
  127. str)))
  128. (define highlight (coloring-procedure (color BOLD)))
  129. (define dim (coloring-procedure (color DARK)))
  130. (define (colorize-matches rules)
  131. "Return a procedure that, when passed a string, returns that string
  132. colorized according to RULES. RULES must be a list of tuples like:
  133. (REGEXP COLOR1 COLOR2 ...)
  134. where COLOR1 specifies how to colorize the first submatch of REGEXP, and so
  135. on."
  136. (lambda (str)
  137. (if (string-index str #\nul)
  138. str
  139. (let loop ((rules rules))
  140. (match rules
  141. (()
  142. str)
  143. (((regexp . colors) . rest)
  144. (match (regexp-exec regexp str)
  145. (#f (loop rest))
  146. (m (let loop ((n 1)
  147. (colors colors)
  148. (result (list (match:prefix m))))
  149. (match colors
  150. (()
  151. (string-concatenate-reverse
  152. (cons (match:suffix m) result)))
  153. ((first . tail)
  154. (loop (+ n 1)
  155. tail
  156. (cons (colorize-string (match:substring m n)
  157. first)
  158. result)))))))))))))
  159. (define-syntax color-rules
  160. (syntax-rules ()
  161. "Return a procedure that colorizes the string it is passed according to
  162. the given rules. Each rule has the form:
  163. (REGEXP COLOR1 COLOR2 ...)
  164. where COLOR1 specifies how to colorize the first submatch of REGEXP, and so
  165. on."
  166. ((_ (regexp colors ...) ...)
  167. (colorize-matches `((,(make-regexp regexp) ,(color colors) ...)
  168. ...)))))