; This module implements a udev rules file parser.
;
; @syntax: (load-rules <file>) - This function loads the nominated
; file into a list headed by the file name, then each "rule" of the
; file as a sub list. Each rule is broken up into a list of terms,
; where each term is a triple of ( <key> <op> <value> ) as a stright
; mapping from the udev rules terms.
;
; The loading deals especially with the concatenation of lines that
; end with "\" with their successor lines, and the splitting of rules
; (the one rule) that starts with LABEL, making that LABEL a separate
; rule.

; This is a term recognition pattern for regex, to capture the keyword
; as $1, the operator as $2  and the value as $3.
(constant 'VP "([^ ,= +!\t]+)([!+=-]?=)\"([^\"]*)\"")

; Parse a udev rule line into a list of the (op var value) terms, in
; order.
(define (parse-variables ln)
  (find-all VP ln (list $1 $2 $3)))

; Parse a rules file into a list of rule lines, discarding comments
; and empty lines, and joining lines with final backslash
(define (parse-rule-file file)
  (let ((out '()) (head ""))
    (dolist (line (parse (read-file file) "\n"))
      (setf head (if (empty? head) line (extend head " " line)))
      (if (empty? head) nil ; skip
        (starts-with head "#") (setf head "")
        (ends-with head "\\") (setf head (chop head))
        (begin (push head out -1) (setf head ""))))
    (unless (empty? head) (push head out -1))
    out))

; Split up a rule sexpr into parts when needed. This returns a list of
; either the given rule as a whole, or in its parts.
(define (split-rule r)
  (letn ((n (ref "LABEL" r)))
    (if (null? n) (list r)
      (append (0 (n 0) r) ((n 0) 1 r) ((+ (n 0) 1) (- (length r) (n 0)) r)))))

; Load a rules file into a list of the filename followed by its rules
; in sepr form.
(define (load-rules-file file)
  (logln "File: " file)
  (cons file (apply extend
                    (map split-rule
                         (map parse-variables (parse-rule-file file))))))

;### The following is the main entry function for this module.
; Load rules files into a list of the filenames followed by their
; rules as sexpr. The filenames are included as markers for ending
; label searches.
(define (load-rules-files files)
  (apply extend (or (map load-rules-file files) '(()))))

; Identify test terms.
(define (test-term? t)
  (when (list? t)
    (or (member (t 1) '("!=" "=="))
        (member (t 0) '("PROGRAM" "PROGRAMS")))))

; List of term keys that should not be considered actions
(constant 'NOTACTION '( "GOTO"
                        "TAG"
                        "NAME"
                        "OPTIONS"
                        "IMPORT{db}"
                        "IMPORT{cmdline}"
                        "IMPORT{builtin}"
                        "IMPORT{parent}"
                        "IMPORT{program}"
                        "ENV{REMOVE_CMD}"
                        "ENV{ID_SERIAL}"
                        "ENV{UDISKS_IGNORE}"
                        "ENV{ID_DRIVE_THUMB}"
                        "ENV{PULSE_IGNORE}"
                        "ENV{PULSE_PROFILE_SET}"
                        "ENV{ID_DRIVE_FLASH_CF}"
                        "ENV{ID_DRIVE_FLASH_MS}"
                        "ENV{ID_DRIVE_FLASH_SM}"
                        "ENV{ID_DRIVE_FLASH_SD}"
                        ))

; Identify action terms.
(define (action-term? t)
  (and (list? t) (not (member (t 0) NOTACTION)) (not (test-term? t))))

; Identify rule with some action term
(define (action-rule? r)
  (and (list? r) (exists action-term? r)))

; Identify a rules filename
(define (rules-file? x)
  (and (ends-with x ".rules") (file? x)))

(setf RULES (load-rules-files (filter rules-file? cmdargs)))

(unless RULES
  (write-line 2 "No rules.")
  (exit 0))

"load-rules.lsp"
