search.scm 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2017-2019, 2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix scripts system search)
  20. #:use-module (guix ui)
  21. #:use-module (guix utils)
  22. #:autoload (guix colors) (color-output? highlight supports-hyperlinks?)
  23. #:autoload (guix diagnostics) (location->hyperlink)
  24. #:use-module (gnu services)
  25. #:use-module (gnu services shepherd)
  26. #:use-module (srfi srfi-1)
  27. #:use-module (srfi srfi-11)
  28. #:use-module (srfi srfi-26)
  29. #:use-module (srfi srfi-34)
  30. #:use-module (ice-9 format)
  31. #:use-module (ice-9 regex)
  32. #:use-module (ice-9 match)
  33. #:export (service-type->recutils
  34. find-service-types
  35. guix-system-search))
  36. ;;; Commentary:
  37. ;;;
  38. ;;; Implement the 'guix system search' command, which searches among the
  39. ;;; available service types.
  40. ;;;
  41. ;;; Code:
  42. (define service-type-name*
  43. (compose symbol->string service-type-name))
  44. (define (service-type-default-shepherd-services type)
  45. "Return the list of Shepherd services created by default instances of TYPE,
  46. provided TYPE has a default value."
  47. (match (guard (c ((service-error? c) #f))
  48. (service type))
  49. (#f '())
  50. ((? service? service)
  51. (let* ((extension (find (lambda (extension)
  52. (eq? (service-extension-target extension)
  53. shepherd-root-service-type))
  54. (service-type-extensions type)))
  55. (compute (and extension (service-extension-compute extension))))
  56. (if compute
  57. (compute (service-value service))
  58. '())))))
  59. (define (service-type-shepherd-names type)
  60. "Return the default names of Shepherd services created for TYPE."
  61. (append-map shepherd-service-provision
  62. (service-type-default-shepherd-services type)))
  63. (define* (service-type->recutils type port
  64. #:optional (width (%text-width))
  65. #:key
  66. (extra-fields '())
  67. (hyperlinks? (supports-hyperlinks? port))
  68. (highlighting identity))
  69. "Write to PORT a recutils record of TYPE, arranging to fit within WIDTH
  70. columns. When HYPERLINKS? is true, emit hyperlink escape sequences when
  71. appropriate. Pass the description through HIGHLIGHTING, a one-argument
  72. procedure that may return a colorized version of its argument."
  73. (define port*
  74. (or (pager-wrapped-port port) port))
  75. (define width*
  76. ;; The available number of columns once we've taken into account space for
  77. ;; the initial "+ " prefix.
  78. (if (> width 2) (- width 2) width))
  79. (define (extensions->recutils extensions)
  80. (let ((list (string-join (map (compose service-type-name*
  81. service-extension-target)
  82. extensions))))
  83. (string->recutils
  84. (fill-paragraph list width*
  85. (string-length "extends: ")))))
  86. (define highlighting*
  87. (if (color-output? port*)
  88. highlighting
  89. identity))
  90. ;; Note: Don't i18n field names so that people can post-process it.
  91. (format port "name: ~a~%"
  92. (highlight (symbol->string (service-type-name type))
  93. port*))
  94. (format port "location: ~a~%"
  95. (or (and=> (service-type-location type)
  96. (if hyperlinks? location->hyperlink location->string))
  97. (G_ "unknown")))
  98. (format port "extends: ~a~%"
  99. (extensions->recutils (service-type-extensions type)))
  100. ;; If possible, display the list of *default* Shepherd service names. Note
  101. ;; that we may not always be able to do this (e.g., if the service type
  102. ;; lacks a default value); furthermore, it could be that the service
  103. ;; generates Shepherd services with different names if we give it different
  104. ;; parameters (this is the case, for instance, for
  105. ;; 'console-font-service-type'.)
  106. (match (service-type-shepherd-names type)
  107. (() #f)
  108. (names (format port "shepherdnames:~{ ~a~}~%" names)))
  109. (when (service-type-description type)
  110. (format port "~a~%"
  111. (highlighting*
  112. (string->recutils
  113. (string-trim-right
  114. (parameterize ((%text-width width*))
  115. (texi->plain-text
  116. (string-append "description: "
  117. (or (and=> (service-type-description type) G_)
  118. ""))))
  119. #\newline)))))
  120. (for-each (match-lambda
  121. ((field . value)
  122. (let ((field (symbol->string field)))
  123. (format port "~a: ~a~%"
  124. field
  125. (fill-paragraph (object->string value) width*
  126. (string-length field))))))
  127. extra-fields)
  128. (newline port))
  129. (define (service-type-description-string type)
  130. "Return the rendered and localised description of TYPE, a service type."
  131. (and=> (service-type-description type)
  132. (compose texi->plain-text G_)))
  133. (define %service-type-metrics
  134. ;; Metrics used to estimate the relevance of a search result.
  135. `((,service-type-name* . 3)
  136. (,service-type-description-string . 2)
  137. (,(lambda (type)
  138. (match (and=> (service-type-location type) location-file)
  139. ((? string? file)
  140. (basename file ".scm"))
  141. (#f
  142. "")))
  143. . 1)))
  144. (define (find-service-types regexps)
  145. "Return a list of service type/score pairs: service types whose name or
  146. description matches REGEXPS sorted by relevance, and their score."
  147. (let ((matches (fold-service-types
  148. (lambda (type result)
  149. (match (relevance type regexps
  150. %service-type-metrics)
  151. ((? zero?)
  152. result)
  153. (score
  154. (cons (cons type score) result))))
  155. '())))
  156. (sort matches
  157. (lambda (m1 m2)
  158. (match m1
  159. ((type1 . score1)
  160. (match m2
  161. ((type2 . score2)
  162. (if (= score1 score2)
  163. (string>? (service-type-name* type1)
  164. (service-type-name* type2))
  165. (> score1 score2))))))))))
  166. (define (guix-system-search . args)
  167. (with-error-handling
  168. (let* ((regexps (map (cut make-regexp* <> regexp/icase) args))
  169. (matches (find-service-types regexps)))
  170. (leave-on-EPIPE
  171. (display-search-results matches (current-output-port)
  172. #:print service-type->recutils
  173. #:regexps regexps
  174. #:command "guix system search")))))