raft/raft.org

711 lines
24 KiB
Org Mode
Raw Normal View History

2023-11-24 21:19:20 +00:00
#+title: Raft - Webdriver implementation in Chicken Scheme
#+author: Daniel Ziltener
#+property: header-args:scheme :session *chicken* :comments both
* Helpers :noexport:
:PROPERTIES:
:header-args:scheme: :prologue "(import (chicken string))"
:END:
** Strip garbage from test results
#+name: test-post
#+begin_src scheme :var input='() :results output
(for-each (lambda (str)
(or (substring=? str ";")
(substring=? str "Note")
(print str)))
(string-split input "\n"))
#+end_src
** Prepare in-line testing
#+name: prep-test
#+begin_src scheme :noweb yes :tangle tests/run.scm :results silent
(import r7rs
test
(chicken base)
(chicken string)
(chicken process)
(chicken gc)
<<dependencies-for-imports()>>
)
#+end_src
* Dependencies
#+name: dependencies
| Dependency | Description |
|-------------+-----------------------------------|
2023-11-24 21:19:20 +00:00
| alist-lib | Handling alists from JSON objects |
| base64 | decoding screenshot data |
2023-11-24 21:19:20 +00:00
| coops | Object system |
| http-client | API interaction |
| intarweb | Supporting HTTP functionality |
| medea | JSON handling |
2023-11-24 21:19:20 +00:00
| srfi-34 | Exception Handling |
| srfi-35 | Exception Types |
| uri-common | Supporting HTTP functionality |
#+name: dependencies-for-egg
#+begin_src emacs-lisp :var tbl=dependencies :colnames yes :results raw :exports none
(mapconcat (lambda (row) (car row)) tbl " ")
#+end_src
#+name: dependencies-for-imports
#+begin_src emacs-lisp :var tbl=dependencies :colnames yes :results raw :exports none
(mapconcat (lambda (row) (concat (car row) "\t\t;;" (cadr row)))
tbl "\n")
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :noweb yes :tangle raft.scm :exports none
(import r7rs)
2023-11-24 21:19:20 +00:00
(define-library (raft)
(import (scheme base))
2023-11-24 21:19:20 +00:00
(export <Raft>
<Gecko>
2023-11-24 21:19:20 +00:00
make-Raft
terminate
initialize-session
terminate-session
set-url
url
back
forward
refresh
title
status
source
screenshot
print-page
execute-async
execute-sync
2023-11-24 21:19:20 +00:00
<RaftTimeouts>
set-timeouts
timeouts
2023-11-24 21:19:20 +00:00
<RaftElement>
css-selector
link-text
partial-link-text
tag-name
xpath
find-element
find-elements
attribute
property
clear
click
computed-label
computed-role
enabled?
selected?
name
rect
screenshot
text
set-value
2023-11-24 21:19:20 +00:00
&raft-exception
raft-exception?
raft-stacktrace
raft-data
<<export-conditions()>>
)
2023-11-24 21:19:20 +00:00
(include "raft-impl.scm"))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :noweb yes :tangle raft-impl.scm :exports none
(import r7rs
(chicken base)
(chicken string)
(chicken process)
(chicken gc)
<<dependencies-for-imports()>>
)
#+end_src
#+begin_src scheme :tangle tests/run.scm :exports none :mkdirp yes
2023-11-24 21:19:20 +00:00
(include-relative "../raft-impl.scm")
#+end_src
* Error Conditions
2023-11-24 21:19:20 +00:00
#+name: raft-exception
#+begin_src scheme :tangle raft-impl.scm
(define-condition-type &raft-exception &error raft-exception?
(stacktrace raft-stacktrace)
(data raft-data))
#+end_src
2023-11-24 21:19:20 +00:00
Every API error code (key "error" in the returned JSON data) gets its own condition type, prefixed by ~&~. They all inherit from ~&raft-exception~.
#+name: error-code-table
| Name | API Error Code |
|---------------------------+---------------------------|
| detached-shadow-root | detached shadow root |
| element-click-intercepted | element click intercepted |
| element-not-interactable | element not interactable |
| insecure-certificate | insecure certificate |
| invalid-argument | invalid argument |
| invalid-cookie-domain | invalid cookie domain |
| invalid-element-state | invalid element state |
| invalid-selector | invalid selector |
| invalid-session-id | invalid session id |
| javascript-error | javascript error |
| move-target-out-of-bounds | move target out of bounds |
| no-such-alert | no such alert |
| no-such-cookie | no such cookie |
| no-such-element | no such element |
| no-such-frame | no such frame |
| no-such-shadow-root | no such shadow root |
| no-such-window | no such window |
| script-timeout | script timeout |
| session-not-created | session not created |
| stale-element-reference | stale element reference |
| timeout | timeout |
| unable-to-capture-screen | unable to capture screen |
| unable-to-set-cookie | unable to set cookie |
| unexpected-alert-open | unexpected alert open |
| unknown-command | unknown command |
| unknown-error | unknown error |
| unknown-method | unknown method |
| unsupported-operation | unsupported operation |
#+name: export-conditions
#+begin_src emacs-lisp :var src=error-code-table :exports none :results raw
(mapconcat
(lambda (row)
(let ((replace `((?n . ,(cl-first row)))))
(format-spec "&%n %n?" replace)))
src "\n")
#+end_src
#+name: define-conditions
#+begin_src emacs-lisp :var src=error-code-table :exports none :results raw
(mapconcat
(lambda (row)
(let ((replace `((?n . ,(cl-first row))
(?c . ,(cl-second row)))))
2023-11-24 21:19:20 +00:00
(format-spec "(define-condition-type &%n &raft-exception %n?)" replace)))
src "\n")
#+end_src
#+name: define-condition-thrower
#+begin_src emacs-lisp :var src=error-code-table :exports none :results raw
2023-11-24 21:19:20 +00:00
(concat "(define (raft-throw data)\n"
" (case (alist-ref data 'error)\n"
(mapconcat
(lambda (row)
(let ((replace `((?n . ,(cl-first row))
(?c . ,(cl-second row)))))
(format-spec " ((\"%c\") (raise (make-condition &%n (alist-ref data 'stacktrace) data)))" replace)))
src "\n")
"\n"
2023-11-24 21:19:20 +00:00
" (else (raise (make-condition &raft-exception (alist-ref data 'stacktrace) data)))\n"
" )\n"
")")
#+end_src
#+name: conditions
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm :noweb yes :exports none
<<define-conditions()>>
<<define-condition-thrower()>>
#+end_src
* WebDriver
2023-11-24 21:19:20 +00:00
The core element of the library is the ~<Raft>~ class and its subclasses. The class has the following fields:
#+name: webdriver-class
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-class <Raft> ()
((browser #f)
(active? #f)
(browser-pid #f)
(server #f)
(port #f)
(session-id #f)
(prefs #f)
(capabilities #f)))
#+end_src
The parent class provides a handful of methods, but does not implement all of them; some are the sole responsibility of the subclass. The ~launch~ method, on the other hand, bears shared responsibility. It sets a finalizer to ensure termination of the web driver process in case the class is disposed of with a still-open driver.
#+name: webdriver-basics
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (launch #:after (instance <Raft>) options)
(set-finalizer! instance (lambda (obj)
(when (slot-value instance 'active?)
(terminate instance)))))
2023-11-24 21:19:20 +00:00
(define-method (terminate (instance <Raft>))
(terminate-session instance)
(process-signal (slot-value instance 'browser-pid))
(set! (slot-value instance 'browser-pid) #f)
(set! (slot-value instance 'active?) #f))
2023-11-24 21:19:20 +00:00
(define-method (construct-capabilities (instance <Raft>) #!optional caps)
(raise 'subclass-responsibility))
2023-11-24 21:19:20 +00:00
(define-method (postprocess-result (instance <Raft>) result)
result)
#+end_src
2023-11-24 21:19:20 +00:00
Main initialization is done by calling the ~make-Raft~ procedure with the respective class name and optionally an alist of options.
#+name: webdriver-init
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define (make-Raft browser #!optional options)
(let ((instance (make browser)))
(launch instance options)
(sleep 1)
instance))
#+end_src
** Geckodriver
The Geckodriver is used to control Firefox.
#+name: geckodriver-basic
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-class <Gecko> (<Raft>)
((browser #:firefox)
(server "127.0.0.1")
(port 4444)))
(define-method (launch (instance <Gecko>) options)
(let ((pid (process-run "geckodriver > /dev/null 2>&1")))
(set! (slot-value instance 'browser-pid) pid)
(set! (slot-value instance 'active?) #t)
(set! (slot-value instance 'capabilities) options)))
#+end_src
The capabilities object for Geckodriver is of the form ={"capabilities": {...}}=.
For more information on capabilities, see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities.
#+name: geckodriver-capabilities
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (construct-capabilities (instance <Gecko>))
(let ((caps (or (slot-value instance 'capabilities) (list))))
`((capabilities . ,caps))))
#+end_src
Sometimes, Geckodriver returns the results of a command in a JSON object with the sole key ="value"=. We have to correct that before returning the data to the user.
#+name: geckodriver-postprocess
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (postprocess-result (instance <Gecko>) result)
(alist-ref/default result 'value result))
#+end_src
#+name: prep-geckodriver-test
#+begin_src scheme :noweb strip-tangle :exports none :post test-post(input=*this*) :results output
<<prep-test>>
2023-11-24 21:19:20 +00:00
<<raft-exception>>
<<conditions>>
<<webdriver-class>>
<<webdriver-basics>>
<<webdriver-init>>
<<geckodriver-basic>>
<<geckodriver-capabilities>>
<<geckodriver-postprocess>>
2023-11-24 21:19:20 +00:00
<<raft-send>>
<<raft-init-session>>
<<raft-term-session>>
#+end_src
* WebDriver API
** Communication
Data is sent to the API via a central class method. For convenience, there is a ~send-with-session~ variant that automatically adds the session id.
2023-11-24 21:19:20 +00:00
#+name: raft-send
#+begin_src scheme :tangle raft-impl.scm
(define-method (send (instance <Raft>) data uri method)
(let* ((remote (string-append "http://" (slot-value instance 'server) ":" (->string (slot-value instance 'port)) "/"))
(result (postprocess-result instance
(with-input-from-request
(make-request #:method method
#:uri (uri-reference (string-append remote uri))
#:headers (headers `((content-type application/json))))
(if data (json->string data) "")
read-json))))
(if (and (list? result) (alist-ref/default result 'error #f))
2023-11-24 21:19:20 +00:00
(raft-throw result)
result)))
2023-11-24 21:19:20 +00:00
(define-method (send-with-session (instance <Raft>) data uri method)
(send instance data (string-append "session/" (slot-value instance 'session-id) "/" uri) method))
#+end_src
** Session management
Session management is very simple. There is just one method to initialize a new session. Everything else is handled automatically.
2023-11-24 21:19:20 +00:00
#+name: raft-init-session
#+begin_src scheme :tangle raft-impl.scm
(define-method (initialize-session (instance <Raft>))
(let ((result (send instance (construct-capabilities instance) "session" 'POST)))
(set! (slot-value instance 'session-id) (alist-ref result 'sessionId))))
#+end_src
2023-11-24 21:19:20 +00:00
#+name: raft-term-session
#+begin_src scheme :tangle raft-impl.scm
(define-method (terminate-session (instance <Raft>))
(when (slot-value instance 'session-id)
(send instance #f (string-append "session/" (slot-value instance 'session-id)) 'DELETE))
(set! (slot-value instance 'session-id) #f))
#+end_src
2023-11-24 21:19:20 +00:00
#+name: raft-session-test
#+begin_src scheme :tangle tests/run.scm :noweb strip-tangle :exports results :post test-post(input=*this*) :results output
<<prep-geckodriver-test>>
(test-group "session"
2023-11-24 21:19:20 +00:00
(let ((browser (make-Raft <Gecko>)))
(test "Initial state" #f (slot-value browser 'session-id))
(test-assert "Session id check" (string? (begin (initialize-session browser) (slot-value browser 'session-id))))
(test-assert "Session id after termination" (eq? #f (begin (terminate-session browser) (slot-value browser 'session-id))))
(terminate browser)))
#+end_src
2023-11-24 21:19:20 +00:00
#+RESULTS: raft-session-test
: -- testing session -----------------------------------------------------------
: Initial state ........................................................ [ PASS]
: Session id check ..................................................... [ PASS]
: Session id after termination ......................................... [ PASS]
: 3 tests completed in 3.788 seconds.
: 3 out of 3 (100%) tests passed.
: -- done testing session ------------------------------------------------------
** API Access Methods
2023-11-24 21:19:20 +00:00
#+name: raft-url
#+begin_src scheme :tangle raft-impl.scm
(define-method (set-url (instance <Raft>) url)
(send-with-session instance `((url . ,url)) "url" 'POST))
2023-11-24 21:19:20 +00:00
(define-method (url (instance <Raft>))
(send-with-session instance #f "url" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+name: raft-url-test
#+begin_src scheme :tangle tests/run.scm :noweb strip-tangle :exports results :post test-post(input=*this*) :results output
<<prep-geckodriver-test>>
2023-11-24 21:19:20 +00:00
<<raft-url>>
(test-group "url"
2023-11-24 21:19:20 +00:00
(let ((browser (make-Raft <Gecko>)))
(test "Initial state" #f (slot-value browser 'session-id))
(test "Navigating to the first website" "http://info.cern.ch/hypertext/WWW/TheProject.html"
(begin (initialize-session browser)
(set-url browser "http://info.cern.ch/hypertext/WWW/TheProject.html")
(url browser)))
(terminate browser)))
#+end_src
2023-11-24 21:19:20 +00:00
#+RESULTS: raft-url-test
: -- testing url ---------------------------------------------------------------
: Initial state ........................................................ [ PASS]
: Navigating to the first website ...................................... [ PASS]
: 2 tests completed in 5.247 seconds.
: 2 out of 2 (100%) tests passed.
: -- done testing url ----------------------------------------------------------
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (back (instance <Raft>))
(send-with-session instance #f "back" 'POST))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (forward (instance <Raft>))
(send-with-session instance #f "forward" 'POST))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (refresh (instance <Raft>))
(send-with-session instance #f "refresh" 'POST))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (title (instance <Raft>))
(send-with-session instance #f "title" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (status (instance <Raft>))
(send-with-session instance #f "status" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (source (instance <Raft>))
(send-with-session instance #f "source" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (screenshot (instance <Raft>))
(base64-decode (send-with-session instance #f "screenshot" 'GET)))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (print-page (instance <Raft>))
(send-with-session instance #f "print" 'POST))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (execute-async (instance <Raft>) script args)
(send-with-session instance `((script . ,script) (args . ,args)) "execute/async" 'POST))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (execute-sync (instance <Raft>) script args)
(send-with-session instance `((script . ,script) (args . ,args)) "execute/sync" 'POST))
#+end_src
** Timeouts
The following timeouts are defined:
- =script=: defaults to 30'000, specifies when to interrupt a script that is being evaluated. A nil value implies that scripts should never be interrupted, but instead run indefinitely.
- =pageLoad=: defaults to 300'000, provides the timeout limit used to interrupt an explicit navigation attempt.
- =implicit=: defaults to 0, specifies a time to wait for the element location strategy to complete when locating an element.
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-class <RaftTimeouts> ()
((script 30000)
(pageLoad 300000)
(implicit 0)))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (extract (instance <RaftTimeouts>))
`((script . ,(slot-value instance 'script))
(pageLoad . ,(slot-value instance 'pageLoad))
(implicit . ,(slot-value instance 'implicit))))
#+end_src
*** Setting and getting timeouts
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (set-timeouts (instance <Raft>) (timeouts <RaftTimeouts>))
(send-with-session instance (extract timeouts) "timeouts" 'POST))
2023-11-24 21:19:20 +00:00
(define-method (timeouts (instance <Raft>))
(let ((result (send-with-session instance #f "timeouts" 'GET)))
2023-11-24 21:19:20 +00:00
(make <RaftTimeouts>
'script (alist-ref result 'script)
'pageLoad (alist-ref result 'pageLoad)
'implicit (alist-ref result 'implicit))))
#+end_src
** Elements
*** Element Class
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-class <RaftElement> ()
((driver #f)
(element #f)))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (send-with-session (instance <RaftElement>) data uri method)
(send-with-session (slot-value instance 'driver) data
(string-append "element/" (slot-value instance 'element) "/" uri)
method))
#+end_src
*** Finding Elements
**** Location Strategies
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define css-selector "css selector")
(define link-text "link text")
(define partial-link-text "partial link text")
(define tag-name "tag name")
(define xpath "xpath")
#+end_src
**** Accessor Methods
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (find-element (instance <Raft>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "element" 'POST)))
2023-11-24 21:19:20 +00:00
(make <RaftElement> 'driver instance 'element (car (alist-values result)))))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (find-elements (instance <Raft>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "elements" 'POST)))
(map
(lambda (elem)
2023-11-24 21:19:20 +00:00
(make <RaftElement> 'driver instance 'element (car (alist-values elem))))
result)))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (find-element (instance <RaftElement>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "element" 'POST)))
2023-11-24 21:19:20 +00:00
(make <RaftElement> 'driver (slot-value instance 'driver) 'element (car (alist-values result)))))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (find-elements (instance <RaftElement>) strategy selector)
(let ((result (send-with-session instance `((using . ,strategy) (value . ,selector)) "elements" 'POST)))
(map
(lambda (elem)
2023-11-24 21:19:20 +00:00
(make <RaftElement> 'driver (slot-value instance 'driver) 'element (car (alist-values elem))))
result)))
#+end_src
*** Working with Elements
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (attribute (instance <RaftElement>) attribute)
(let ((result (send-with-session instance #f
(string-append "attribute/" attribute)
'GET)))
(if (equal? "true" result)
#t
result)))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (property (instance <RaftElement>) property)
(send-with-session instance #f (string-append "property/" property) 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (clear (instance <RaftElement>))
(send-with-session instance #f "clear" 'POST))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (click (instance <RaftElement>))
(send-with-session instance #f "click" 'POST))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (computed-label (instance <RaftElement>))
(send-with-session instance #f "computedlabel" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (computed-role (instance <RaftElement>))
(send-with-session instance #f "computedrole" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (enabled? (instance <RaftElement>))
(send-with-session instance #f "enabled" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (selected? (instance <RaftElement>))
(send-with-session instance #f "selected" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (name (instance <RaftElement>))
(send-with-session instance #f "name" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (rect (instance <RaftElement>))
(send-with-session instance #f "rect" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (screenshot (instance <RaftElement>))
(base64-decode (send-with-session instance #f "screenshot" 'GET)))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (text (instance <RaftElement>))
(send-with-session instance #f "text" 'GET))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :tangle raft-impl.scm
(define-method (set-value (instance <RaftElement>) value)
(send-with-session instance `((text . ,value)) "value" 'POST))
#+end_src
* About This Egg
2023-11-24 21:19:20 +00:00
#+begin_src scheme :noweb yes :tangle raft.egg :exports none
;; -*- scheme -*-
((author "Daniel Ziltener")
(synopsis "A WebDriver API implementation for Chicken")
(category web)
(license "BSD")
(version <<latest-release()>>)
(dependencies r7rs <<dependencies-for-egg()>>)
(test-dependencies test)
(components
(extension webdriver
(csc-options "-X" "r7rs" "-R" "r7rs" "-sJ"))))
#+end_src
#+begin_src scheme :tangle tests/run.scm :exports none
(test-exit)
#+end_src
** Source
2023-11-24 21:19:20 +00:00
The source is available at [[https://gitea.lyrion.ch/Chicken/raft]].
** Author
Daniel Ziltener
** Version History
#+name: version-history
| 0.5 | Initial Release |
#+name: gen-releases
#+begin_src emacs-lisp :var vers=version-history :results raw :exports none
(mapconcat (lambda (row) (concat "(release \"" (number-to-string (car row)) "\") ;; " (cadr row)))
vers "\n")
#+end_src
#+name: latest-release
#+begin_src emacs-lisp :var vers=version-history :exports none :results code
(number-to-string (caar vers))
#+end_src
2023-11-24 21:19:20 +00:00
#+begin_src scheme :noweb yes :tangle raft.release-info :exports none
;; -*- scheme -*-
2023-11-24 21:19:20 +00:00
(repo git "https://gitea.lyrion.ch/Chicken/raft")
(uri targz "https://gitea.lyrion.ch/Chicken/raft/archive/{egg-release}.tar.gz")
<<gen-releases()>>
#+end_src
** License
#+begin_src fundamental :tangle LICENSE
Copyright (C) 2023 Daniel Ziltener
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#+end_src