From: Ralph Ronnquist Date: Mon, 8 May 2023 00:40:41 +0000 (+1000) Subject: initial X-Git-Url: https://git.rrq.au/?a=commitdiff_plain;h=59aa1b0c1699a713fe6fead3fb2ec89083a5fdff;p=rrq%2Fnewlisp%2Fenitool.git initial --- 59aa1b0c1699a713fe6fead3fb2ec89083a5fdff diff --git a/enitool.lsp b/enitool.lsp new file mode 100755 index 0000000..af06741 --- /dev/null +++ b/enitool.lsp @@ -0,0 +1,155 @@ +#!/usr/bin/newlisp +# +# Helper tool to browse and edit the ifupdown configuration +# uses iselect, ed, nano and sudo +# +# Extra iselect commands: +# # = toggle commenting of configuration block +# d = delete empty line +# e = edit the whole file +# +# right-arrow or return = follow to sourced file, or edit the current block +# left-arrow or q = go up or exit + +(signal 2 (fn (x) (exit))) + +(when (!= "0" (if (exec "id -u") ($it 0) "")) + (let ((SUDO (if (exec "command -v sudo") ($it 0) "/usr/bin/sudo")) + (ED (format "env EDITOR=%s" (or (env "EDITOR") "nano"))) ) + (wait-pid (process (join (flat (list SUDO ED (main-args))) " "))) + (exit 0))) + +(constant + ;; all "block starters", including blank lines + 'ENI-KEY '( "iface" "mapping" "auto" "allow-\\w*" "rename" + "source" "source-directory" "$") + ;; regex to identify block starters + 'ENI-HEAD (format "^\\s*#?\\s*(%s)" (join ENI-KEY "|")) + 'ENI-COMMENT "^\\s*#" + 'EDITOR (or (env "EDITOR") "nano") + 'PROC (if (exec (format "command -v %s" EDITOR)) ($it 0) "/bin/nano") + ) + +(define (is-eni-key PAT S) + (when (regex PAT S 0) true)) + +(define (is-eni-comment S) + (is-eni-key ENI-COMMENT S)) + +(define (istrue? A B) + (list A B) (= A B true)) + +(define (eni-starters) ; DATA + (flat (ref-all ENI-HEAD DATA is-eni-key))) + +;; Pull out the block headed by the B line. If this head is a blank +;; line, then the block includes preceeding comment and the blank line +;; only. Otherwise it includes preceeding comment, head line and the +;; following mix of non-head lines and comment lines (i.e. up to next +;; head line). FROM is the line after the prior block, and it is moved +;; to end of this block. +(define (sub-divide-DATA B (E (length DATA))) ; DATA FROM + (let ((SLICE (fn (F B E) (append (list F B E) (slice DATA F (- E F)))))) + (SLICE FROM B (set 'FROM (if (empty? (DATA B)) (+ B 1) E))))) + +;; Read the "interfaces file" and split up into its "blocks" +;; * ( | )* +(define (read-eni NAME) + (letn ((DATA (parse (read-file NAME) "\n")) + (BEG (eni-starters)) + (FROM 0)) + (setf LASTENI (map sub-divide-DATA BEG (1 BEG))) + ;;(map println LASTENI) + ;;(read-line) + LASTENI + )) + +(define (add-selector X) + (cons (format "%s" (X 0) (X 1)) (2 X))) + +;; Find the definition block spanning line I in file FILE +(define (find-block I FILE) + ;;(println (list 'find-block I FILE)) + (exists (fn (B) (< I (B 2))) (read-eni FILE))) + +############################################################ +### Interactive actions + +## PATH holds interactive state as a stack of [pos file] +(setf PATH '(( 0 "/etc/network/interfaces" )) ) + +; Edit a file +(define (edit-file I FILE) + (wait-pid (process (format "%s +%d %s" PROC (int I) FILE)))) + +(define (ensure-newline TXT) + (if (empty? TXT) "" (ends-with TXT "\n") TXT (string TXT "\n"))) + +(define (update-file B E TXT FILE) + (let ((DATA (parse (read-file FILE) "\n"))) + (write-file TXT (string (join (0 B DATA) "\n" true) + (ensure-newline (read-file TXT)) + (join (E DATA) "\n"))) + (exec (format "mv %s %s" TXT FILE)) + )) + +(define (key-command-select I FILE) ; PATH + (letn ((BLOCK (find-block (- (int I) 1) FILE)) + (TMP "/tmp/enitool/tmp.conf") + (HEAD (BLOCK (- (BLOCK 1) (BLOCK 0) -3))) + (TAG (or (and (regex "^#?(\\w*) (.*)" HEAD 0) $1) "#")) + (VALUE $2)) + (case TAG + ("source" (push (list 0 VALUE) PATH)) + (true (write-file TMP (join (3 BLOCK) "\n" true)) + (let ((F (file-info TMP 6))) + (edit-file 1 TMP) + (when (!= F (file-info TMP 6)) + (update-file (BLOCK 0) (BLOCK 2) TMP FILE))) + )) + )) + +(define (delete-block-maybe I FILE) + (let ((BLOCK (find-block (- (int I) 1) FILE)) + (TMP "/tmp/enitool/tmp.conf")) + (when (= (3 BLOCK) '("")) + (exec (format "ed -s %s" FILE) + (format "%dd\nw\n" (+ 1 (BLOCK 0))))))) + +(define (toggle-commenting I FILE) + (let ((BLOCK (find-block (- (int I) 1) FILE)) + (TMP "/tmp/enitool/tmp.conf")) + (letn ((H (- (BLOCK 1) (BLOCK 0))) + (TXT (3 BLOCK)) + (toggle (if (starts-with (TXT H) "#") + (fn (X) (if (starts-with X "#") (1 X) X)) + (fn (X) (string "#" X))))) + (write-file TMP (ensure-newline + (string (join (0 H TXT) "\n" true) + (join (map toggle (H TXT)) "\n")))) + (update-file (BLOCK 0) (BLOCK 2) TMP FILE) + ))) + +(define (command-dispatch CMD FILE) + (when (regex "([^:]+):([^:]+):(\\S*)\\s*(.*)" CMD 0) + (setf (PATH 0 0) (int $1)) + (cond + ((member $2 '("KEY_RIGHT" "RETURN")) (key-command-select $1 FILE)) + ((= $2 "d") (delete-block-maybe $1 FILE)) + ((= $2 "#") (toggle-commenting $1 FILE)) + ((= $2 "e") (edit-file $1 FILE)) + ))) + +(define (iselect POS FILE) + (exec (format "iselect -n '%s' -t '%s' -a -P -K '-k#' -kd -ke -p %d < %s" + "enitool" FILE (int POS) FILE))) + +(change-dir "/etc/network") +(make-dir "/tmp/enitool") + +(while PATH + (if (apply iselect (PATH 0)) + (command-dispatch ($it 0) (PATH 0 1)) + (pop PATH))) + +(exit 0)