guix-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Guix Data Service client module


From: Ludovic Courtès
Subject: Guix Data Service client module
Date: Sun, 27 Jun 2021 23:23:43 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux)

Hello Guix!

Here’s a client module for the Guix Data Service, allowing you to access
a subset of the Guix Data Service interfaces from the comfort of your
REPL.

I had it sitting in my source tree for a while and Chris sent me an
impressive shell one-liner that made me want to try from Scheme:

  wget 
"https://data.guix-patches.cbaines.net/revision/47f85c53d954f857b45cebefee27ec512d917484/lint-warnings.json?locale=en_US.UTF-8&linter=input-labels&field=linter&field=message&field=location";
 -O - | jq -r '.lint_warnings | .[] | .package.name' | sort | uniq | wc -l

Turns out we can do the same in two long lines of Scheme!

--8<---------------cut here---------------start------------->8---
scheme@(guix data-service)> (define s (open-data-service 
"https://data.guix-patches.cbaines.net";))
scheme@(guix data-service)> (length (delete-duplicates (map 
lint-warning-package (revision-lint-warnings s 
"47f85c53d954f857b45cebefee27ec512d917484" "input-labels"))))
$6 = 3560
--8<---------------cut here---------------end--------------->8---

(That counts the number of packages at that revision that have one or
more warnings from the new ‘input-labels’ lint checker.)

We can do other things, such as browsing package versions:

--8<---------------cut here---------------start------------->8---
scheme@(guix data-service)> (define s (open-data-service 
"https://data.guix.gnu.org";))
scheme@(guix data-service)> (package-version-branches (car (package-versions 
(lookup-package s "emacs"))))
$9 = (#<<branch> name: "master" repository-id: 1>)
scheme@(guix data-service)> (package-version-history s (car $9) "emacs")
$10 = (#<<package-version-range> version: "27.2" first-revision: #<<revision> 
commit: "cc33f50d0e2a7835e99913226cb4c4b0e9e961ae" date: #<date nanosecond: 0 
second: 54 minute: 30 hour: 20 day: 25 month: 3 year: 2021 zone-offset: 0>> 
last-revision: #<<revision> commit: "364b56124b88398c199aacbfd4fdfc9a1583e634" 
date: #<date nanosecond: 0 second: 31 minute: 16 hour: 13 day: 27 month: 6 
year: 2021 zone-offset: 0>>> #<<package-version-range> version: "27.1" 
first-revision: #<<revision> commit: "36a09d185343375a5cba370431870f9c4435d623" 
date: #<date nanosecond: 0 second: 52 minute: 16 hour: 4 day: 28 month: 8 year: 
2020 zone-offset: 0>> last-revision: #<<revision> commit: 
"ac29d37e2ffd7a85adfcac9be4d5bce018289bec" date: #<date nanosecond: 0 second: 2 
minute: 36 hour: 17 day: 25 month: 3 year: 2021 zone-offset: 0>>> 
#<<package-version-range> version: "26.3" first-revision: #<<revision> commit: 
"43412ab967ee00789fe933f916d804aed9961c57" date: #<date nanosecond: 0 second: 
29 minute: 36 hour: 3 day: 30 month: 8 year: 2019 zone-offset: 0>> 
last-revision: #<<revision> commit: "bf19d5e4b26a2e38fe93a45f9341e14476ea5f82" 
date: #<date nanosecond: 0 second: 19 minute: 50 hour: 21 day: 27 month: 8 
year: 2020 zone-offset: 0>>> #<<package-version-range> version: "26.2" 
first-revision: #<<revision> commit: "5069baedb8a902c3b1ea9656c11471658a1de56b" 
date: #<date nanosecond: 0 second: 8 minute: 46 hour: 22 day: 12 month: 4 year: 
2019 zone-offset: 0>> last-revision: #<<revision> commit: 
"02c61278f1327d403f072f42e6b92a1dc62fc93a" date: #<date nanosecond: 0 second: 
35 minute: 44 hour: 0 day: 30 month: 8 year: 2019 zone-offset: 0>>> 
#<<package-version-range> version: "26.1" first-revision: #<<revision> commit: 
"897f303d2fa61497a931cf5fcb43349eb5f44c14" date: #<date nanosecond: 0 second: 
47 minute: 31 hour: 7 day: 1 month: 1 year: 2019 zone-offset: 0>> 
last-revision: #<<revision> commit: "ee6c4b62b88640f3828cf73a30377124e16cb95f" 
date: #<date nanosecond: 0 second: 51 minute: 8 hour: 20 day: 12 month: 4 year: 
2019 zone-offset: 0>>>)
--8<---------------cut here---------------end--------------->8---

Now all we need to do is plug it into the right tools and enjoy!

Ludo’.

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix 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 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (guix data-service)
  #:use-module (json)
  #:use-module (web client)
  #:use-module (web response)
  #:use-module (web uri)
  #:use-module ((guix diagnostics) #:select (location))
  #:use-module (srfi srfi-9)
  #:use-module (srfi srfi-19)
  #:use-module (srfi srfi-71)
  #:use-module (rnrs bytevectors)
  #:use-module (ice-9 binary-ports)
  #:use-module (ice-9 match))

(define-json-mapping <repository> make-repository repository?
  json->repository
  (id             repository-id)
  (label          repository-label)
  (url            repository-url)
  (branches       repository-branches "branches"
                  (const 'not-implemented)))   ;FIXME: another kind of branch!

(define-json-mapping <branch> make-branch branch?
  json->branch
  (name           branch-name)
  (repository-id  branch-repository-id "git_repository_id"))

(define-json-mapping <package-version> make-package-version
  package-version?
  json->package-version
  (string    package-version-string "version")
  (branches  package-version-branches "branches"
             (lambda (vector)
               (map json->branch (vector->list vector)))))

(define-json-mapping <package> make-package package?
  json->package
  (name      package-name)
  (versions  package-versions "versions"
             (lambda (vector)
               (map json->package-version (vector->list vector)))))

(define (utc-date date)
  "Return DATE with its timezone offset zeroed."
  (make-date (date-nanosecond date) (date-second date)
             (date-minute date) (date-hour date)
             (date-day date) (date-month date) (date-year date)
             0))

(define (string->date* str)
  (utc-date (string->date str "~Y-~m-~d ~H:~M:~S"))) ;assume dates are UTC

(define-json-mapping <revision> make-revision revision?
  json->revision
  (commit   revision-commit)
  (date     revision-date "datetime" string->date*))

(define-json-mapping <package-version-range>
  make-package-version-range package-version-range?
  json->package-version-range
  (version          package-version-range-version)
  (first-revision   package-version-range-first-revision
                    "first_revision" json->revision)
  (last-revision    package-version-range-last-revision
                    "last_revision" json->revision))

(define-json-mapping <build>
  make-build build?
  json->build
  (server-id  build-server-id "build_server_id")
  (id         build-id "build_server_build_id")
  (time       build-time "timestamp"
              (lambda (str)
                (utc-date
                 (string->date str "~Y-~m-~dT~H:~M:~S")))))

(define-json-mapping <channel-instance>
  make-channel-instance channel-instance?
  json->channel-instance
  (system      channel-instance-system)
  (derivation  channel-instance-derivation)
  (builds      channel-instance-builds "builds"
               (lambda (vector)
                 (map json->build (vector->list vector)))))

(define (json->location alist)
  (location (assoc-ref alist "file")
            (assoc-ref alist "line-number")
            (assoc-ref alist "column-number")))

(define-json-mapping <lint-warning> make-lint-warning lint-warning?
  json->lint-warning
  (package    lint-warning-package "package"
              (lambda (alist)
                (assoc-ref alist "name")))
  (package-version lint-warning-package-version "package"
                   (lambda (alist)
                     (assoc-ref alist "version")))
  (message    lint-warning-message)
  (location   lint-warning-location "location" json->location))


;;;
;;; Calling the Guix Data Service.
;;;

;; Connection to an instance of the Data Service.
(define-record-type <data-service>
  (data-service socket uri)
  data-service?
  (socket   data-service-socket)
  (uri      data-service-uri))

(define (open-data-service url)
  "Open a connection to the Guix Data Service instance at URL."
  (let ((uri (string->uri url)))
    (data-service (open-socket-for-uri uri) uri)))

(define (make-data-service-uri service path)
  (build-uri
   (uri-scheme (data-service-uri service))
   #:host (uri-host (data-service-uri service))
   #:port (uri-port (data-service-uri service))
   #:path path))

(define (discard port n)
  "Read N bytes from PORT and discard them."
  (define bv (make-bytevector 4096))

  (let loop ((n n))
    (unless (zero? n)
      (match (get-bytevector-n! port bv 0
                                (min n (bytevector-length bv)))
        ((? eof-object?) #t)
        (read (loop (- n read)))))))

(define (call service path)
  (let* ((uri (make-data-service-uri service path))
         (response port
                   (http-get uri
                             #:port (data-service-socket service)
                             #:keep-alive? #t
                             #:headers '((Accept . "application/json"))
                             #:streaming? #t)))
    (unless (= 200 (response-code response))
      (when (response-content-length response)
        (discard port (response-content-length response)))
      (throw 'data-service-client-error uri response))
    port))

(define (lookup-package service name)
  "Lookup package NAME and return a package record."
  (json->package (call service (string-append "/package/" name))))

(define (lookup-repository service id)
  "Lookup the repository with the given ID, an integer, and return it."
  (json->repository
   (call service (string-append "/repository/" (number->string id)))))

(define (package-version-history service branch package)
  "Return a list of package version ranges for PACKAGE, a string, on BRANCH, a
<branch> record."
  ;; http://data.guix.gnu.org/repository/1/branch/master/package/emacs.json
  (map json->package-version-range
       (let ((result (json->scm
                      (call service
                            (string-append "/repository/"
                                           (number->string
                                            (branch-repository-id branch))
                                           "/branch/"
                                           (branch-name branch)
                                           "/package/" package)))))
         (vector->list (assoc-ref result "versions")))))

(define (revision-channel-instances service commit)
  "Return the channel instances for COMMIT."
  (let ((result (json->scm
                 (call service
                       (string-append "/revision/" commit
                                      "/channel-instances")))))
    (map json->channel-instance
         (vector->list (assoc-ref result "channel_instances")))))

(define* (revision-lint-warnings service commit #:optional linter)
  "Return lint warnings for COMMIT.  If LINTER is given, only show warnings
for the given linter--e.g., 'description'."
  (let ((result (json->scm
                 (call service
                       (string-append "/revision/" commit
                                      "/lint-warnings"
                                      (if linter
                                          (string-append "?linter=" linter)
                                          ""))))))
    (map json->lint-warning
         (vector->list (assoc-ref result "lint_warnings")))))

reply via email to

[Prev in Thread] Current Thread [Next in Thread]