From 7dbac023bb1f68d3c93184a8716c9e41070a22d4 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Mon, 17 Aug 2026 22:00:11 +0200 Subject: [PATCH] Initial import of rash-git-cli --- LICENSE | 29 ++++++++------- README.md | 25 ++++++++++++- examples/rash.rkt | 8 +++++ info.rkt | 21 +++++++++++ main.rkt | 43 +++++++++++++++++++++++ scribblings/rash-git-cli.scrbl | 37 ++++++++++++++++++++ tests/rash.rkt | 64 ++++++++++++++++++++++++++++++++++ 7 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 examples/rash.rkt create mode 100644 info.rkt create mode 100644 main.rkt create mode 100644 scribblings/rash-git-cli.scrbl create mode 100644 tests/rash.rkt diff --git a/LICENSE b/LICENSE index 3a87446..152ea81 100644 --- a/LICENSE +++ b/LICENSE @@ -1,18 +1,21 @@ MIT License -Copyright (c) 2026 hans +Copyright (c) 2026 Hans Dijkema -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the -following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a2300bf..c118f34 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ # rash-git-cli -A rash module of the racket module git-cli \ No newline at end of file +Rash line-mode integration for `git-cli`. + +`git-cli` remains a pure Racket package. Install and require `rash-git-cli` +only when the Rash command form is wanted. + +```racket +#lang rash + +(require rash-git-cli) + +git status +git add -A +git log --oneline -5 +git commit -m "Update documentation" + +;; The ordinary git-cli procedure is still available in Racket expressions. +(git 'status) +``` + +Bare words in the Rash line form are quoted as command arguments. Quoted +strings and parenthesized Racket expressions keep their normal Racket meaning. + +The package depends on `git-cli`, `rash`, and `linea`. `git-cli` does not +depend on this package. diff --git a/examples/rash.rkt b/examples/rash.rkt new file mode 100644 index 0000000..e4a9285 --- /dev/null +++ b/examples/rash.rkt @@ -0,0 +1,8 @@ +#lang rash + +(require rash-git-cli) + +git status +git log --oneline -5 + +(git 'status) diff --git a/info.rkt b/info.rkt new file mode 100644 index 0000000..3f55f45 --- /dev/null +++ b/info.rkt @@ -0,0 +1,21 @@ +#lang info + +(define collection "rash-git-cli") +(define pkg-desc "Rash line-mode integration for git-cli") +(define version "0.1.0") +(define pkg-authors '("Hans Dijkema")) +(define license 'MIT) + +(define deps + '("base" + "git-cli" + "linea" + "rash")) + +(define build-deps + '("rackunit-lib" + "racket-doc" + "scribble-lib")) + +(define scribblings + '(("scribblings/rash-git-cli.scrbl" () ("rash-git-cli")))) diff --git a/main.rkt b/main.rkt new file mode 100644 index 0000000..a0e4f84 --- /dev/null +++ b/main.rkt @@ -0,0 +1,43 @@ +#lang racket/base + +(require (except-in git-cli git) + (only-in git-cli [git git/procedure]) + (for-syntax racket/base + syntax/parse + linea/line-macro-prop)) + +(provide (except-out (all-from-out git-cli) git/procedure) + git) + +;; `git` remains the ordinary git-cli procedure in Racket expression mode. +;; In Linea/Rash line mode, bare command words are quoted and passed to that +;; same procedure. +(begin-for-syntax + (define (git-argument->expression stx) + (syntax-parse stx + [(quote value) + stx] + [id:id + #`(quote #,(syntax-e #'id))] + [_ + stx])) + + (struct git-line-binding (target) + #:property prop:procedure + (λ (self stx) + (syntax-parse stx + [id:id + (git-line-binding-target self)] + [(id argument ...) + #`(#,(git-line-binding-target self) argument ...)])) + #:property prop:line-macro + (λ (self stx) + (syntax-parse stx + [(_ argument ...) + (define arguments + (map git-argument->expression + (syntax->list #'(argument ...)))) + #`(#,(git-line-binding-target self) #,@arguments)])))) + +(define-syntax git + (git-line-binding #'git/procedure)) diff --git a/scribblings/rash-git-cli.scrbl b/scribblings/rash-git-cli.scrbl new file mode 100644 index 0000000..b882208 --- /dev/null +++ b/scribblings/rash-git-cli.scrbl @@ -0,0 +1,37 @@ +#lang scribble/manual + +@(require (for-label racket/base + git-cli)) + +@title[#:tag "top"]{rash-git-cli} +@author["Hans Dijkema / hans@dijkewijk.nl"] + +@section{Rash integration} + +The @tt{rash-git-cli} package adds a Linea/Rash line meaning to the +@racket[git] procedure from @racketmodname[git-cli]. + +@verbatim{ +#lang rash + +(require rash-git-cli) + +git status +git add -A +git log --oneline -5 +git commit -m "Update documentation" +} + +In an ordinary Racket expression, @racket[git] remains the first-class +procedure exported by @racketmodname[git-cli]: + +@racketblock[ +(git 'status) +(procedure? git) +] + +Bare words in line mode are quoted before they are passed to @racket[git]. +Quoted strings and parenthesized Racket expressions retain their normal Racket +meaning. + +The base @tt{git-cli} package has no dependency on Rash, Linea, or this package. diff --git a/tests/rash.rkt b/tests/rash.rkt new file mode 100644 index 0000000..c4a8d3b --- /dev/null +++ b/tests/rash.rkt @@ -0,0 +1,64 @@ +#lang racket + +(require rackunit + racket/file + linea/read) + +(define tmp (make-temporary-file "rash-git-cli~a" 'directory)) + +(dynamic-wind + void + (λ () + (define module-path (build-path tmp "Rash.rkt")) + + (call-with-output-file module-path + #:exists 'truncate/replace + (λ (out) + (displayln "#lang rash" out) + (displayln "(require rash-git-cli)" out))) + + (define module-name `(file ,(path->string module-path))) + + (parameterize ([current-directory tmp]) + (dynamic-require module-name #f)) + + (define ns (module->namespace module-name)) + (parameterize ([current-directory tmp] + [current-namespace ns]) + ;; In expression mode the same binding remains callable as git-cli's + ;; first-class Git procedure. + (check-true (eval #'(procedure? git))) + + ;; The Rash line form initializes a repository. + (define init-line + (linea-read-syntax 'drracket-interaction + (open-input-string "git init\n"))) + (eval init-line) + (check-true (directory-exists? (build-path tmp ".git"))) + + (check-true (eval #'(git 'config 'set! "user.name" "Rash Test"))) + (check-true (eval #'(git 'config 'set! "user.email" "rash@example.invalid"))) + + (call-with-output-file (build-path tmp "test.txt") + #:exists 'truncate/replace + (λ (out) + (displayln "rash" out))) + + (eval + (linea-read-syntax 'drracket-interaction + (open-input-string "git add test.txt\n"))) + + (check-true + (eval + (linea-read-syntax 'drracket-interaction + (open-input-string + "git commit -m \"Rash commit\"\n")))) + + (check-equal? + (eval + (linea-read-syntax 'drracket-interaction + (open-input-string "git status\n"))) + '()) + (check-equal? (eval #'(git 'status)) '()))) + (λ () + (delete-directory/files tmp #:must-exist? #f)))