blob: 538844e25c95e8e3addd37a5bedabe254e24d2ba (
plain) (
tree)
|
|
;;; buildbot-utils.el --- Commonly used utilities. -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc.
;;
;; This file is part of buildbot.el.
;;
;; buildbot.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; buildbot.el 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
;; Affero General Public License for more details.
;;
;; You should have received a copy of the GNU Affero General Public
;; License along with buildbot.el. If not, see
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Commonly used utilities.
;;; Code:
(require 'json)
(defvar buildbot-client-buffer-name "*buildbot api*"
"Name of the buffer recording buildbot API calls.")
(defun buildbot-parse-http-header (text)
"Parse the http header TEXT."
(let ((status) (fields))
(with-temp-buffer
(insert text)
(goto-char (point-min))
(re-search-forward "^HTTP.*\\([0-9]\\{3\\}\\).*$")
(setq status (match-string 1))
(while (re-search-forward "^\\(.*?\\): \\(.*\\)$" nil t)
(push (cons (intern (match-string 1)) (match-string 2)) fields)))
(list (cons 'status status) (cons 'fields fields))))
(defun buildbot-delete-http-header ()
"Delete the http header from a response buffer."
(save-excursion
(goto-char (point-min))
(kill-region (point) (progn (re-search-forward "\r?\n\r?\n")
(point)))))
(defun buildbot-url-fetch-internal (url processor &optional
decompression with-header)
"Fetch from URL and process the response payload using PROCESSOR.
PROCESSOR is a function that takes no argument and processes the
current buffer.
With non-nil DECOMPRESSION, decompress the response.
With non-nil WITH-HEADER, include the header in the result."
(with-current-buffer (get-buffer-create buildbot-client-buffer-name)
(goto-char (point-max))
(insert "[" (current-time-string) "] Request: " url "\n"))
(with-current-buffer (url-retrieve-synchronously url t)
(let ((header) (status) (fields))
(buildbot-delete-http-header)
(goto-char (point-min))
(setq header (buildbot-parse-http-header (car kill-ring))
status (alist-get 'status header)
fields (alist-get 'fields header))
(with-current-buffer buildbot-client-buffer-name
(insert "[" (current-time-string) "] Response: " status "\n"))
(when decompression
(call-process-region (point) (point-max) "gunzip" t t t)
(goto-char (point-min)))
(call-interactively 'delete-trailing-whitespace)
(if (string= status "200")
(unless (= (point) (point-max))
(if with-header
(list
(cons 'header fields)
(cons 'json (funcall processor)))
(funcall processor)))
(error "HTTP error: %s" (buffer-substring (point) (point-max)))))))
(defun buildbot-url-fetch-json (url &optional decompression with-header)
"Fetch and parse a json object from URL.
With non-nil DECOMPRESSION, decompress the response.
With non-nil WITH-HEADER, include the header in the result."
(buildbot-url-fetch-internal url 'json-read decompression with-header))
(defun buildbot-url-fetch-raw (url &optional decompression with-header)
"Fetch from URL.
With non-nil DECOMPRESSION, decompress the response.
With non-nil WITH-HEADER, include the header in the result."
(buildbot-url-fetch-internal url 'buffer-string decompression
with-header))
(defun buildbot-format-attr (attr)
"Format an alist ATTR into a url query string."
(string-join
(mapcar
(lambda (pair)
(format "%s=%s" (car pair) (cdr pair)))
(seq-filter #'cdr attr))
"&"))
(defun buildbot-format-epoch-time (epoch)
"Format an EPOCH."
(format-time-string "%Y-%m-%d %a %H:%M:%S %Z" (encode-time
(decode-time epoch))))
(defun buildbot-build-status (build)
"Get the status of a BUILD."
(let ((state (alist-get 'state_string build)))
(cond ((equal state "build successful")
'success)
((string-suffix-p "(failure)" state)
'failure)
(t 'pending))))
(defun buildbot-step-guess-status (step)
"Guess the status of a STEP."
(let ((state (alist-get 'state_string step)))
(cond ((string-suffix-p "(warnings)" state)
'pending)
((string-suffix-p "(failure)" state)
'failure)
((string-suffix-p "done" state)
'success)
((string-suffix-p "ing" state)
'pending)
((string-suffix-p "finished" state)
'success)
(t 'success))))
(defun buildbot-status-face (status)
"Get the face of STATUS."
(pcase status
('success 'success)
('failure 'error)
(_ 'warning)))
(defun buildbot-get-build-stats (builds)
"Get the aggregated build stats of BUILDS."
(let ((results (copy-tree '((success . 0)
(failure . 0)
(pending . 0))))
(status))
(seq-do
(lambda (build)
(setq status (buildbot-build-status build))
(setf (alist-get status results)
(1+ (alist-get status results))))
builds)
results))
(defun buildbot-get-revision-info-from-change (change)
"Get the revision info from a CHANGE."
(list
(assq 'revision change)
(assq 'author change)
(cons 'created-at
(buildbot-format-epoch-time
(alist-get 'when_timestamp change)))
(assq 'comments change)))
(defun buildbot-get-revision-and-changes-info (changes)
"Get the revision-info and builds from a set of CHANGES.
The changes should be of the same revision."
(let* ((first-change (elt changes 0))
(revision-info (buildbot-get-revision-info-from-change first-change))
(changes-info
(mapcar
(lambda (change)
(append
(list
(assq 'branch change)
(assq 'builds change)
(assq 'revision first-change))
(when-let ((builds (assq 'builds change)))
`((build-stats . ,(buildbot-get-build-stats
(cdr builds)))))))
changes)))
`((revision-info . ,revision-info) (changes-info . ,changes-info))))
(provide 'buildbot-utils)
;;; buildbot-utils.el ends here
|