feat(tools/magrathea): add weirdly overengineered target spec parser

i'm not sure what happened here, but it works (yes, the fancy target
printing is completely unnecessary, but oh well):

    #;152> (parse-target "foo")
    #target(foo)

    #;153> (parse-target "//foo")
    #target(//foo)

    #;154> (parse-target "//foo/bar")
    #target(//foo/bar)

    #;155> (parse-target "//foo/bar/")
    #target(//foo/bar)

    #;156> (parse-target "//foo/bar:baz")
    #target(//foo/bar:baz)

    #;157> (parse-target "//foo/bar/:baz")
    #target(//foo/bar:baz)

    #;158> (parse-target "//foo/bar:")
    (error . "unexpected end of input while parsing virtual target")

    #;159> (parse-target "//foo//")
    (error . "unexpected root-anchor while parsing normal target")

the most notable thing is that trailing slashes are allowed in the
physical targets, since people may be autocompleting these on the
shell from folder names.

Change-Id: I32975ad77fe2a327130dc9574011fe92cce49f84
Reviewed-on: https://cl.tvl.fyi/c/depot/+/4393
Tested-by: BuildkiteCI
Reviewed-by: grfn <grfn@gws.fyi>
Reviewed-by: wpcarro <wpcarro@gmail.com>
This commit is contained in:
Vincent Ambo 2021-12-17 21:17:28 +03:00 committed by tazjin
parent 564ad52b30
commit cecc249c96

View file

@ -8,8 +8,9 @@
;; magrathea enables this, but with nix-y monorepos. ;; magrathea enables this, but with nix-y monorepos.
(import (chicken base) (import (chicken base)
(chicken io) (chicken format)
(chicken irregex) (chicken irregex)
(chicken port)
(chicken process) (chicken process)
(chicken process-context) (chicken process-context)
(chicken string) (chicken string)
@ -39,6 +40,109 @@ file all feedback on b.tvl.fyi
USAGE USAGE
) )
;; parse target definitions. trailing slashes on physical targets are
;; allowed for shell autocompletion.
;;
;; component ::= any string without "/" or ":"
;;
;; physical-target ::= <component>
;; | <component> "/"
;; | <component> "/" <physical-target>
;;
;; virtual-target ::= ":" <component>
;;
;; relative-target ::= <physical-target>
;; | <virtual-target>
;; | <physical-target> <virtual-target>
;;
;; root-anchor ::= "//"
;;
;; target ::= <relative-target> | <root-anchor> <relative-target>
;; read a path component until it looks like something else is coming
(define (read-component first port)
(let ((keep-reading?
(lambda () (not (or (eq? #\/ (peek-char port))
(eq? #\: (peek-char port))
(eof-object? (peek-char port)))))))
(let reader ((acc (list first))
(condition (keep-reading?)))
(if condition (reader (cons (read-char port) acc) (keep-reading?))
(list->string (reverse acc))))))
;; read something that started with a slash. what will it be?
(define (read-slash port)
(if (eq? #\/ (peek-char port))
(begin (read-char port)
'root-anchor)
'path-separator))
;; read any target token and leave port sitting at the next one
(define (read-token port)
(match (read-char port)
[#\/ (read-slash port)]
[#\: 'virtual-separator]
[other (read-component other port)]))
;; read a target into a list of target tokens
(define (read-target target-str)
(call-with-input-string
target-str
(lambda (port)
(let reader ((acc '()))
(if (eof-object? (peek-char port))
(reverse acc)
(reader (cons (read-token port) acc)))))))
(define-record target absolute components virtual)
(define-record-printer (target t out)
(fprintf out (conc "#target("
(if (target-absolute t) "//" "")
(string-intersperse (target-components t) "/")
(if (target-virtual t) ":" "")
(or (target-virtual t) "")
")")))
;; parse and validate a list of target tokens
(define parse-tokens
(lambda (tokens #!optional (mode 'root) (acc (make-target #f '() #f)))
(match (cons mode tokens)
;; absolute target
[('root . ('root-anchor . rest))
(begin (target-absolute-set! acc #t)
(parse-tokens rest 'root acc))]
;; relative target minus potential garbage
[('root . (not ('path-separator . _)))
(parse-tokens tokens 'normal acc)]
;; virtual target
[('normal . ('virtual-separator . rest))
(parse-tokens rest 'virtual acc)]
[('virtual . ((? string? v)))
(begin
(target-virtual-set! acc v)
acc)]
;; chomp through all components and separators
[('normal . ('path-separator . rest)) (parse-tokens rest 'normal acc)]
[('normal . ((? string? component) . rest))
(begin (target-components-set! acc (append (target-components acc) (list component)))
(parse-tokens rest 'normal acc ))]
;; nothing more to parse and not in a weird state, all done, yay!
[('normal . ()) acc]
;; oh no, we ran out of input too early :(
[(_ . ()) `(error . ,(format "unexpected end of input while parsing ~s target" mode))]
;; something else was invalid :(
[_ `(error . ,(format "unexpected ~s while parsing ~s target" (car tokens) mode))])))
(define (parse-target target)
(parse-tokens (read-target target)))
;; return the current repository root as a string ;; return the current repository root as a string
(define mg--repository-root #f) (define mg--repository-root #f)
(define (repository-root) (define (repository-root)