;; kenku --- crawl and reproduce github actions ;; Copyright © 2026 bdunahu ;; ;; This program 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. ;; ;; This program 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 this program. If not, see . (define-module (src utils) #:use-module (srfi srfi-1) #:use-module (ice-9 match) #:use-module (web client) #:use-module (web response) #:use-module (rnrs bytevectors) #:use-module (json) #:use-module (ice-9 receive) #:use-module (ice-9 textual-ports) #:use-module (ice-9 pretty-print) #:use-module (ice-9 regex) #:use-module (ice-9 popen) #:export (default-headers url-exists? url->scm shell->str normalize-file append-to-values mkdir-p filter-actions-on-regex)) (define default-headers `((Accept . "application/vnd.github+json") (Authorization . ,(string-append "Bearer " (getenv "TOKEN"))) (User-Agent . "curl 8.6.0"))) ;i lied (define (url-exists? url) (receive (status body) (http-request url #:headers default-headers) (equal? 200 (response-code status)))) (define* (url->scm url #:key (headers default-headers)) (receive (status body) (http-request url #:headers headers) (json-string->scm (utf8->string body)))) (define (shell->str . args) (let* ((port (apply open-pipe* (cons OPEN_READ args))) (contents (get-string-all port))) (close-pipe port) contents)) (define (normalize-file file) (let* ((str (call-with-input-file file get-string-all)) (str (string-filter (lambda (c) (not (member c (list #\, #\" #\' #\return)))) str)) (str (string-join (delete-duplicates (string-split str #\newline)) "\n")) (output (open-file file "w"))) (display str output) (close output))) (define (append-to-values hashtable k v) (let ((existing (hash-ref hashtable k '()))) (hash-set! hashtable k (cons v existing)) hashtable)) (define (mkdir-p dir) "Yoinked from https://codeberg.org/guix/guix." (define absolute? (string-prefix? "/" dir)) (define not-slash (char-set-complement (char-set #\/))) (let loop ((components (string-tokenize dir not-slash)) (root (if absolute? "" "."))) (match components ((head tail ...) (let ((path (string-append root "/" head))) (catch 'system-error (lambda () (mkdir path) (loop tail path)) (lambda args (if (= EEXIST (system-error-errno args)) (loop tail path) (apply throw args)))))) (() #t)))) (define (filter-actions-on-regex file file->regex parse-f) "This procedure is for the bash drop-ins only, which do not filter their outputs into files, but rather output lines which need to be filtered based on regex. FILE: the file to be filtered FILE->REGEX: an alist mapping file names to the regex each item (usually a line in FILE, should match) would need to match for inclusion to said file name. PARSE-F: A function describing how the contents of FILE should be parsed into items." (let ((ht (make-hash-table))) (define (hash-actions-to-regex actions) (for-each (lambda (s) (for-each (lambda (pair) (let ((file (car pair)) (regex (cdr pair))) (when (string-match regex s) (append-to-values ht file s)))) file->regex)) actions) ht) (define (hash->files) (hash-for-each (lambda (file actions) (mkdir-p (dirname file)) (let ((output (open-file file "w"))) (map (lambda (a) (format output "~a~%" a)) actions) (close output))) ht)) (let* ((str (call-with-input-file file get-string-all)) (actions (parse-f str))) (hash-actions-to-regex actions) (hash->files))))