diff --git a/CHANGELOG b/CHANGELOG index 4d60445..cac6886 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,18 @@ +0.1.4 + +Replaced files-for-regexp, list-dir-re, list-dir-re-r, and filter-dirs with +list-dir/files, list-files, and list-dirs. +All three helpers support optional #:recursive #t traversal. +Non-recursive results use complete paths so they can be passed directly to +rm-f and rm-rf. +Updated tests, examples, README, and Scribble documentation. + +0.1.3 + +Added recursive and non-recursive regexp directory helpers: +files-for-regexp, list-dir-re, list-dir-re-r, and filter-dirs. +Added documentation and an example for regexp-based cleanup with rm-f/rm-rf. + 0.1.2 Makefile loading no longer starts a build automatically. diff --git a/README.md b/README.md index 3d17551..e69f293 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ raco pkg install For a versioned archive, give the package name explicitly: ```text -raco pkg install --name racket-makefile racket-makefile-0.1.2.zip +raco pkg install --name racket-makefile racket-makefile-0.1.4.zip ``` ## Running targets @@ -78,6 +78,36 @@ registers the targets. Then use the Interactions window: Bare identifiers in `make` are target names, so no quote is needed. For example, `(make clean)` selects the target named `clean`. +## Regexp-based cleanup + +For cleanup rules that need more control than globs, `racket-makefile` also +provides small directory/regexp helpers. The results are ordinary path lists, +so they can be passed directly to `rm-f` and `rm-rf` with `apply`: + +```racket +#lang racket-makefile + +(default-target all) +(phony all clean) + +(target all + (displayln "use (make clean)")) + +(target clean + (display "cleaning up...") + (apply rm-rf + (list-dirs "." #px"compiled$" #:recursive #t)) + (apply rm-f + (list-files "." #px"(?i:(?:[.]bak|~)$)" #:recursive #t)) + (displayln "done.")) +``` + +`list-dir/files` returns matching files and directories. `list-files` and +`list-dirs` restrict the result to files or directories. All three scan one +directory level by default; use `#:recursive #t` to walk the complete tree. +Non-recursive results are returned as complete paths, so the lists can be fed +directly to `rm-f` or `rm-rf` with `apply`. + ## Generated targets Because the rest of the language is ordinary Racket, targets can be generated @@ -101,7 +131,7 @@ with normal definitions and loops: Dependency expressions may produce lists; they are recursively flattened. The language adds `target`, `deps`, `phony`, `default-target`, `make`, `run`, -`rm-f`, `rm-rf`, `cleanup`, `$target`, `$deps` and `$<`. Everything else is -ordinary Racket. +`rm-f`, `rm-rf`, `cleanup`, `list-dir/files`, `list-files`, `list-dirs`, +`$target`, `$deps` and `$<`. Everything else is ordinary Racket. See the installed `racket-makefile` Scribble documentation for the full API. diff --git a/examples/cleanup-regexp.rkt b/examples/cleanup-regexp.rkt new file mode 100644 index 0000000..da11054 --- /dev/null +++ b/examples/cleanup-regexp.rkt @@ -0,0 +1,15 @@ +#lang racket-makefile + +(default-target all) +(phony all clean) + +(target all + (displayln "use (make clean)")) + +(target clean + (display "cleaning up...") + (apply rm-rf + (list-dirs "." #px"compiled$" #:recursive #t)) + (apply rm-f + (list-files "." #px"(?i:(?:[.]bak|~)$)" #:recursive #t)) + (displayln "done.")) diff --git a/info.rkt b/info.rkt index 32db99c..2df97cb 100644 --- a/info.rkt +++ b/info.rkt @@ -1,7 +1,7 @@ #lang info (define pkg-authors '(hnmdijkema)) -(define version "0.1.2") +(define version "0.1.4") (define license 'MIT) (define collection "racket-makefile") (define pkg-desc diff --git a/main.rkt b/main.rkt index add7177..8319f17 100644 --- a/main.rkt +++ b/main.rkt @@ -17,6 +17,9 @@ rm-f rm-rf cleanup + list-dir/files + list-files + list-dirs $target $deps $<) diff --git a/private/commands.rkt b/private/commands.rkt index c7db493..d946b59 100644 --- a/private/commands.rkt +++ b/private/commands.rkt @@ -10,7 +10,10 @@ (provide run rm-f rm-rf - cleanup) + cleanup + list-dir/files + list-files + list-dirs) (define (recipe-value who parameter description) (define value (parameter)) @@ -80,3 +83,19 @@ [(file link) (rm-f path)] [else (void)])) (void)) + +(define (list-dir/files directory regexp #:recursive [recursive #f]) + (filter (lambda (path) + (regexp-match? regexp path)) + (if recursive + (for/list ([path (in-directory directory)]) + path) + (directory-list directory #:build? #t)))) + +(define (list-files directory regexp #:recursive [recursive #f]) + (filter file-exists? + (list-dir/files directory regexp #:recursive recursive))) + +(define (list-dirs directory regexp #:recursive [recursive #f]) + (filter directory-exists? + (list-dir/files directory regexp #:recursive recursive))) diff --git a/scrbl/racket-makefile.scrbl b/scrbl/racket-makefile.scrbl index 664a06b..c45e459 100644 --- a/scrbl/racket-makefile.scrbl +++ b/scrbl/racket-makefile.scrbl @@ -5,6 +5,7 @@ (only-in racket-makefile target deps phony default-target make run rm-f rm-rf cleanup + list-dir/files list-files list-dirs $target $deps $<))) @title{racket-makefile} @@ -31,7 +32,7 @@ raco pkg install For a versioned archive, specify the package name explicitly: @verbatim{ -raco pkg install --name racket-makefile racket-makefile-0.1.2.zip +raco pkg install --name racket-makefile racket-makefile-0.1.4.zip } After installation a makefile can start with: @@ -224,6 +225,59 @@ For example: "**.css"))) ] +@section{Regexp directory helpers} + +@defproc[(list-dir/files [directory path-string?] + [regexp regexp?] + [#:recursive recursive any/c #f]) + list?]{ +Returns entries below @racket[directory] that match @racket[regexp]. By default +only the direct contents of @racket[directory] are inspected. With +@racket[#:recursive #t], the complete tree is walked using +@racket[in-directory], and both files and directories can be returned. + +For a non-recursive listing, complete paths are built from @racket[directory], +so the result can be passed directly to file operations such as +@racket[rm-f] and @racket[rm-rf]. +} + +@defproc[(list-files [directory path-string?] + [regexp regexp?] + [#:recursive recursive any/c #f]) + list?]{ +Like @racket[list-dir/files], but keeps only paths for which +@racket[file-exists?] is true. +} + +@defproc[(list-dirs [directory path-string?] + [regexp regexp?] + [#:recursive recursive any/c #f]) + list?]{ +Like @racket[list-dir/files], but keeps only paths for which +@racket[directory-exists?] is true. +} + +These helpers make regexp-based cleanup concise. For example: + +@racketblock[ +(default-target all) +(phony all clean) + +(target all + (displayln "use (make clean)")) + +(target clean + (display "cleaning up...") + (apply rm-rf + (list-dirs "." #px"compiled$" #:recursive #t)) + (apply rm-f + (list-files "." #px"(?i:(?:[.]bak|~)$)" #:recursive #t)) + (displayln "done.")) +] + +The second regular expression is case-insensitive and matches names ending in +@tt{.bak} or @tt{~}. + @section{Using ordinary Racket} No separate make programming language is introduced. Definitions, functions, diff --git a/tests/helpers.rkt b/tests/helpers.rkt index cca38f9..a5b364d 100644 --- a/tests/helpers.rkt +++ b/tests/helpers.rkt @@ -2,7 +2,9 @@ (require rackunit racket/file - (only-in racket-makefile rm-f rm-rf cleanup)) + (only-in racket-makefile + rm-f rm-rf cleanup + list-dir/files list-files list-dirs)) (define tmp (make-temporary-file "racket-makefile-test~a" 'directory)) @@ -25,6 +27,44 @@ (check-false (file-exists? html)) (check-true (file-exists? txt)) + ;; Directory/regexp helpers. + (define top-bak (build-path tmp "TOP.BAK")) + (call-with-output-file top-bak (lambda (out) (display "bak" out))) + (define compiled (build-path tmp "a" "compiled")) + (make-directory* compiled) + (define nested-bak (build-path tmp "a" "nested.bak")) + (call-with-output-file nested-bak (lambda (out) (display "bak" out))) + + ;; Non-recursive results are complete paths and include both files and dirs. + (check-equal? (list-files tmp #px"(?i:[.]bak$)") + (list top-bak)) + (check-equal? (list-dirs tmp #px"a$") + (list (build-path tmp "a"))) + + ;; Recursive variants select the requested kind directly. + (define recursive-baks + (list-files tmp #px"(?i:[.]bak$)" #:recursive #t)) + (check-not-false (member top-bak recursive-baks)) + (check-not-false (member nested-bak recursive-baks)) + (check-equal? (list-dirs tmp #px"compiled$" #:recursive #t) + (list compiled)) + + ;; list-dir/files returns both matching files and directories. + (define all-a + (list-dir/files tmp #px"(?i:(?:a|[.]bak))$" #:recursive #t)) + (check-not-false (member (build-path tmp "a") all-a)) + (check-not-false (member top-bak all-a)) + (check-not-false (member nested-bak all-a)) + + ;; Intended makefile idiom: feed the path lists directly into variadic + ;; rm-rf and rm-f. + (apply rm-rf (list-dirs tmp #px"compiled$" #:recursive #t)) + (check-false (directory-exists? compiled)) + + (apply rm-f (list-files tmp #px"(?i:[.]bak$)" #:recursive #t)) + (check-false (file-exists? top-bak)) + (check-false (file-exists? nested-bak)) + (rm-rf (build-path tmp "a")) (check-false (directory-exists? (build-path tmp "a")))) (lambda ()