[racketscript] Dynamically require all racket files in a directory

I'm trying to dynamically require all rkt files in a directory without using any bindings of them, because they have conflicts.
The tree is the following

|- index.rkt
|--- commands
  |- echo.rkt
  |- another-cmd.rkt
  |- ...

so the idea is that each of this commands export three variables name, description and a function execute, I'm looking for a way to tell racks to include these files automatically, I tried to hack together a require transformer, yet it seems that I need at least one variable in the body of the module for it to work, I can't just visit the modules.

This is what I tried using
visit-in.rkt

#lang racket/base
(require racket/require
         (for-syntax racket/base
                     racket/path
                     racket/require-transform
                     syntax/parse))

(provide visit-in visit-all-rkt-in)

(define-syntax visit-in
  (make-require-transformer
    (lambda (stx)
      (syntax-parse stx
        [(_ path)
         (expand-import #'(only-in path))]
        [(_ subs ...+)
         (let-values ([(_ import-sources) ; only visit sources
                       (expand-import #'(multi-in subs ...))])
           (values '() import-sources))]
        ))))

(define-syntax visit-all-rkt-in
  (make-require-transformer
   (lambda (stx)
     (syntax-parse stx
       [(_ path:string)
        (define path/source (syntax-source #'path))
        (unless (and path/source (path-string? path/source) (absolute-path? path/source))
          (raise-syntax-error 'visit-all-rkt-in "expected path? source location" #'path))

        (let-values ([(base _name _dir?) (split-path path/source)])
          (let* ([is-rkt? (lambda (x) (path-has-extension? x #".rkt"))]
                 [dirlist (directory-list (build-path base (syntax-e #'path)))])
            (expand-import
             #`(visit-in
                path
                #,(map path->string (filter is-rkt? dirlist))))))]))))

and in "index.rkt"

#lang racketscript/base

(require "visit-in.rkt"
         (visit-all-rkt-in "commands"))

these two also do not work

(define-syntax (require-folder stx)
  (syntax-parse stx
    [(_ path:string)
     (let ([base-src (syntax-source #'path)]
           [path/string (syntax-e #'path)])
       (let-values ([(base _name _dir?) (split-path base-src)])
         (with-syntax
             ([(mod-paths ...)
               (for/list ([p (in-list (directory-list (build-path base path/string)))]
                          #:when (path-has-extension? p #".rkt"))
                 (path->string (build-path path/string p)))])
           #'(begin
               (define x ($/require mod-paths))
               ...))))]))

(define-syntax (require-folder2 stx)
  (syntax-parse stx
    [(_ path:string)
     (let ([base-src (syntax-source #'path)]
           [path/string (syntax-e #'path)])
       (let-values ([(base _name _dir?) (split-path base-src)])
         (with-syntax
             ([(mod-paths ...)
               (for/list ([p (in-list (directory-list (build-path base path/string)))]
                          #:when (path-has-extension? p #".rkt"))
                 (path->string (build-path path/string p)))])
           #'(begin
               (require mod-paths)
               ...))))]))

(require-folder2 "commands")
(require-folder "commands")

Okay, I'm virtually certain that I'm missing something obvious here, but is there a reason that you can't use e.g.

(dynamic-require file #f)

?

yes, I should have put it in the title that what I need is a solution for racketscript (although I did put it in the racketscript category, but I get that is not very visible)

Basically, racketscript compiles to javascript, and it seems that if you don't use any identifiers from a module, it doesnt register them as dependencies.

After much testing, and trying to understand racketscript's compiler internals, it turns out it only registers a module as part of the dependency graph if one of the identifiers in the body of the module comes from another module, so the solution ends up like this.

(define-syntax (require-folder* stx)
  (syntax-parse stx
    [(_ path:string)
     #:with name-bind (format-id this-syntax "name")
     (let ([base-src (syntax-source #'path)]
           [path/string (syntax-e #'path)])
       (let-values ([(base _name _dir?) (split-path base-src)])
         (with-syntax*
             ([(mod-paths ...)
               (for/list ([p (in-list (directory-list (build-path base path/string)))]
                          #:when (path-has-extension? p #".rkt"))
                 (path->string (build-path path/string p)))]
              [(tmp ...) (generate-temporaries #'(mod-paths ...))])
           #'(begin
               (begin (require (only-in mod-paths [name-bind tmp])) tmp)
               ...))))]))

You need to have one provided binding in common throughout all the files you want to have compiled, and actually use it,. Inside the macro we use the only-in form to avoid any issues with conflicting bindings.

Not the prettiest solution, but it works.

Note: $/require seems to only be intended to work with external libraries.

Glad to hear you found a workaround. Does RacketScript's design make sense here, or is this something that should change?

I opened two issues that might make this easier for newcomers (like me).

so hopefully an alternative to above could be made available to users.

2 Likes