;; ;; brmsaptool.scm ;; ;; ISC License ;; ;; Copyright 2023 Brmlab, z.s. ;; Dominik Pantůček ;; ;; Permission to use, copy, modify, and/or distribute this software ;; for any purpose with or without fee is hereby granted, provided ;; that the above copyright notice and this permission notice appear ;; in all copies. ;; ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ;; (import (chicken condition) (chicken file) (chicken pathname) (chicken file posix) (chicken io) (chicken string) (chicken process-context)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Static default configuration (define *members-directory* (make-parameter "members")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Testing (define-syntax with-handler (syntax-rules () ((_ handler body ...) (call-with-current-continuation (lambda (k) (with-exception-handler (lambda (x) (k (handler x))) (lambda () body ...))))))) (define-syntax unit-test (syntax-rules () ((_ name condition) (if (with-handler (lambda (x) #f) (lambda () condition)) (display ".") (error 'unit-test name))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Dictionary ;; Returns an empty dictionary represented as empty list. (define (make-dict) '()) ;; Checks whether given dictionary d contains the key k. (define (dict-has-key? d k) (if (assq k d) #t #f)) ;; Retrieves the value for key k from dictionary d. If third argument ;; is provided it is used as default value in case the key does not ;; exist. If only two arguments are given and the key does not exist, ;; raises an error. (define (dict-ref d k . r) (let ((p (assq k d))) (if p (cdr p) (if (null? r) (error 'dict-ref "Key does not exist" k) (car r))))) ;; Returns a new dictionary based on d with key k removed. If it ;; doesn't contain the key, an error is raised. (define (dict-remove d k) (let loop ((s d) (r '()) (e #t)) (if (null? s) (if e (error 'dict-remove "Key does not exist" k) r) (if (eq? (caar s) k) (loop (cdr s) r #f) (loop (cdr s) (cons (car s) r) e))))) ;; Adds a new value v under the key k to the dictionary d possibly ;; overwriting any value which has been stored under the key ;; before. Returns the updated dictionary. (define (dict-set d k v) (let ((dr (let loop ((s d) (r '())) (if (null? s) r (if (eq? (caar s) k) (loop (cdr s) r) (loop (cdr s) (cons (car s) r))))))) (cons (cons k v) dr))) ;; Returns the list of keys stored in given dictionary. (define (dict-keys d) (map car d)) (define (dict-tests!) (display "[test] dict ") (unit-test 'make-dict (null? (make-dict))) (unit-test 'dict-ref-nonexistent (with-handler (lambda (x) #t) (dict-ref (make-dict) 'nonexistent) #f)) (unit-test 'dict-ref-default (dict-ref (make-dict) 'nonexistent #t)) (unit-test 'dict-set-nonexistent (equal? (dict-set (make-dict) 'nonexistent 1) '((nonexistent . 1)))) (unit-test 'dict-set-existent (equal? (dict-set (dict-set (make-dict) 'existent 1) 'existent 2) '((existent . 2)))) (unit-test 'dict-remove-nonexistent (with-handler (lambda (x) #t) (dict-remove (make-dict) 'nonexistent) #f)) (unit-test 'dict-remove-existing (null? (dict-remove (dict-set (make-dict) 'existing 1) 'existing))) (unit-test 'dict-keys (equal? (dict-keys (dict-set (make-dict) 'existing 1)) '(existing))) (print " ok.")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Months support ;; Returns true if this is a valid month representation - a list with ;; two integer elements within the allowed range. (define (month-valid? m) (and (list? m) (car m) (cdr m) (cadr m) (nil? (cddr m)) (integer? (car m)) (integer? (cadr m)) (>= (car m 1000)) (<= (car m 9999)) (>= (cadr m 1)) (<= (cadr m 12)))) ;; Converts string in a format YYYY-MM to valid month. Returns #f if ;; the conversion fails. (define (string->month s) (let ((l (string-split s "-"))) (if (or (not l) (nil? l) (nil? (cdr l)) (not (nil? (cddr l)))) #f (let ((y (string->number (car l))) (m (string->number (cadr l)))) (if (and y m) (let ((M (list y m))) (if (valid-month? M) M #f)) #f))))) ;; Formats (valid) month as YYYY-MM string (define (month->string M) (if (month-valid? M) (let ((y (car s)) (m (cadr s))) (sprintf "~A-~A~A" y (if (< m 10) " " "") m)) (error 'string->month "Invalid month" M))) ;; Returns true if both arguments are a valid month and are equal (define (month=? m n) (and (valid-month? m) (valid-month? n) (equal? m n))) ;; Returns true if the first argument is a month in the past of the ;; second argument month (define (monthmonth (equal? (string->month "2023-01" '(2023 1)))) (unit-test 'string->month-bad-month (not (string->month "2023-13"))) (unit-test 'string->month-nonumber-year (not (string->month "YYYY-01"))) (unit-test 'string->month-nonumber-month (not (string->month "2023-MMM"))) (unit-test 'month->string (equal? (month->string '(2023 1)) "2023-01")) (unit-test 'month->string-bad-year (with-handler (lambda (x) #t) (month->string '(999 12)) #f)) (unit-test 'month->string-bad-month (with-handler (lambda (x) #t) (month->string '(2023 13)) #f)) (unit-test 'monthnumber lnk)) (dfnn (string->number dfn)) (id (or dfnn lnkn)) (name (if lnkn dfn lnk))) (loop (cdr ri) (cons (list (cons 'id id) (cons 'name name) (cons 'file dfn)) ds)))))) ;; Parses given key-value line. Key is up to first space, value is the ;; rest of the line. If the line doesn't contain anything, returns #f. (define (parse-member-line l) ;;(print 'pm) ;;(print l) (let ((sp (string-split l " "))) (and sp (not (null? sp)) (list (string->symbol (car sp)) (string-intersperse (cdr sp)))))) ;; TODO: student and suspend special handling - should create list of ;; (start date) (stop date) which will be later sorted by date and ;; computation performed (define (parse-member-lines ls) (let loop ((ls ls) (r (make-dict))) ;;(print '---) ;;(print r) (if (null? ls) r (let ((p (parse-member-line (car ls)))) ;;(print p) (loop (cdr ls) (if p (apply dict-set r p) r)))))) ;; Loads lines from given file in (*members-directory*) and parses ;; them. (define (load-member-file fn) (let* ((ffn (make-pathname (*members-directory*) fn)) (f (open-input-file ffn)) (ls (read-lines f))) (parse-member-lines ls))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Run everything ;; Print banner (print "brmsaptool 0.1 (c) 2023 Brmlab, z.s.") (newline) ;; Run tests (print "Running self-tests:") (dict-tests!) (month-tests!) (print "All self-tests ok!") (newline) ;; Perform requested action ;(print (expand-members-raw-index (load-members-raw-index))) (print (load-member-file "joe"))