;; ;; 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 format) (chicken sort) (chicken time) (chicken time posix) (chicken process-context) dictionary month period member-file) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Static default configuration (define *members-directory* (make-parameter "members")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Members database ;; Loads all symlinks from (*members-directory*) returning a list of ;; pairs (name . destination) (define (load-members-raw-index) (let loop ((fns (directory (*members-directory*))) (rs '())) (if (null? fns) (let () (display "-") rs) (let* ((fn (car fns)) (ffn (make-pathname (*members-directory*) fn)) (sl (if (symbolic-link? ffn) (read-symbolic-link ffn) #f))) (loop (cdr fns) (if sl (cons (cons fn sl) rs) rs)))))) ;; Converts the raw members index to a list of dictionaries with keys ;; 'id, 'name and 'file. File names are without directory element. (define (expand-members-raw-index ri) (let loop ((ri ri) (ds '())) (if (null? ri) (let () (display "*") ds) (let* ((mp (car ri)) (lnk (car mp)) (dfn (cdr mp)) (lnkn (string->number 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)))))) ;; Adds the 'info key to all expanded index entries by loading ;; appropriate 'file key file from the members directory. (define (load-members-from-expanded-index ei) (let loop ((ei ei) (mdb '())) (if (null? ei) mdb (let ((mi (car ei))) (let ((mid (load-member-file (make-pathname (*members-directory*) (dict-ref mi 'file))))) (loop (cdr ei) (cons (dict-set mi 'info mid) mdb))))))) ;; Loads all member information from given members database. (define (load-members) (load-members-from-expanded-index (expand-members-raw-index (load-members-raw-index)))) ;; Gets member based by generic predicate (define (find-member-by-predicate mdb pred) (let loop ((mdb mdb)) (if (null? mdb) #f (let ((mr (car mdb))) (if (pred mr) mr (loop (cdr mdb))))))) ;; Gets member record by member key (define (find-member-by-key mdb key val) (find-member-by-predicate mdb (lambda (mr) (equal? (dict-ref mr key) val)))) ;; Gets member record by member id (from file/symlink) (define (find-member-by-id mdb id) (find-member-by-key mdb 'id id)) ;; Gets member record by member name (from file/symlink) (define (find-member-by-name mdb name) (find-member-by-key mdb 'name name)) ;; Gets member record by member nick in member file the key 'nick (define (find-member-by-nick mdb nick) (find-member-by-predicate mdb (lambda (mr) (equal? (dict-ref (dict-ref mr 'info) 'nick) nick)))) ;; Returns the list of all members ids (define (list-members-ids mdb) (map (lambda (mr) (dict-ref mr 'id)) mdb)) ;; Returns the list of all file names in members database (define (list-members-names mdb) (map (lambda (mr) (dict-ref mr 'name)) mdb)) ;; Returns the list of all members nicks (define (list-members-nicks mdb) (map (lambda (mr) (dict-ref (dict-ref mr 'info) 'nick)) mdb)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Member predicates (define (member-suspended? mr) #f) (define (member-student? mr) #f) (define (member-destroyed? mr) #f) ;; Returns true if the member is neither suspended nor destroyed (define (member-active? mr) (not (or (member-suspended? mr) (member-destroyed? mr)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Command-line parsing ;; Prints simple help (define (print-help) (print "Command-line arguments: -h prints this help -M dir specifies the members database directory -mi id member id -mn nick member nick ") (exit 0)) ;; Consumes given number of arguments from the list and returns the ;; remainder of the list and a list of arguments consumed. (define (consume-args args num) (let loop ((args args) (res '()) (num num)) (if (= num 0) (list args (reverse res)) (if (null? args) (error 'consume-args "Not enough arguments" num) (loop (cdr args) (cons (car args) res) (- num 1)))))) ;; Gets command-line arguments after the "--" of csi (not useful when ;; compiled) (define (get-command-line-arguments . explicit-argv) (let* ((args (if (null? explicit-argv) (argv) explicit-argv)) (rargs (member "--" args))) (if rargs (cdr rargs) (cdr args)))) ;; Performs the actual parsing based on specification. (define (do-parse-command-line specs) (let loop ((args (get-command-line-arguments))) (when (not (null? args)) (let* ((arg (car args)) (specp (assoc arg specs))) (when (not specp) (error 'parse-command-line "Unknown argument" arg)) (let* ((proc (cadr specp)) (info (procedure-information proc)) (nargs (- (length info) 1)) (aargsl (consume-args (cdr args) nargs)) (args (car aargsl)) (aargs (cadr aargsl))) (apply proc aargs) (loop args)))))) ;; Simple syntax wrapper for command-line arguments specification (define-syntax parse-command-line (syntax-rules () ((_ ((arg proc) ...)) (do-parse-command-line `((arg ,proc) ...))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Run everything (define action (make-parameter #f)) (define member-parm (make-parameter #f)) ;; Print banner (print "brmsaptool 0.1 (c) 2023 Brmlab, z.s.") (newline) ;; Handle options (parse-command-line (("-h" print-help) ("-M" (lambda (dn) (*members-directory* dn))) ("-mi" (lambda (id) (action 'member-by-id) (member-parm (string->number id)))) ("-mn" (lambda (nick) (action 'member-by-nick) (member-parm nick))) )) ;; Run tests (print "Running self-tests:") (dictionary-tests!) (month-tests!) (period-tests!) (print "All self-tests ok!") (newline) ;; Load the members database (display "Loading members ") (define MDB (load-members)) (print " ok.") (print "Members in database: " (length MDB)) (newline) ;; Perform requested action (case (action) ((member-by-id member-by-nick) (let ((mr (if (eq? (action) 'member-by-id) (find-member-by-id MDB (member-parm)) (find-member-by-nick MDB (member-parm))))) (if mr (let* ((id (dict-ref mr 'id)) (name (dict-ref mr 'name)) (info (dict-ref mr 'info)) (sinfo (sort info (lambda (a b) (stringstring (car a)) (symbol->string (car b))))))) (print "User " id " alias " name) (let loop ((sinfo sinfo)) (when (not (null? sinfo)) (let* ((kv (car sinfo)) (k (car kv)) (v (cdr kv))) (print " " k ":\t" (if (member k '(student suspend)) (periods->string v) v)) (loop (cdr sinfo)))))) (let () (print "No such member " (member-parm) "."))))) ((#f) (print "No action specified.")))