diff --git a/project.rkt b/project.rkt index 9b381e0..a6b3374 100644 --- a/project.rkt +++ b/project.rkt @@ -1,18 +1,36 @@ #lang racket/base (require "world.rkt" sugar/define sugar/coerce) -(define (paths? x) (and (list? x) (andmap path? x))) +(define (complete-paths? x) (and (list? x) (andmap complete-path? x))) (define/contract+provide (get-directory-require-files source-path) ; keep contract local to ensure coercion - (coerce/path? . -> . (or/c #f paths?)) - (define possible-requires (list (simplify-path (build-path source-path 'up world:directory-require)))) - (and (andmap file-exists? possible-requires) possible-requires)) + (coerce/path? . -> . (or/c #f complete-paths?)) + + (define (dirname path) + (let-values ([(dir name dir?) (split-path path)]) + dir)) + + (define (find-upward filename-to-find) + (parameterize ([current-directory (dirname (->complete-path source-path))]) + (let loop ([dir (current-directory)][path (string->path filename-to-find)]) + (and dir ; dir is #f when it hits the top of the filesystem + (let ([completed-path (path->complete-path path)]) + (if (file-exists? completed-path) + (simplify-path completed-path) + (loop (dirname dir) (build-path 'up path)))))))) + + (define require-filenames (list world:directory-require)) + (define not-false? (λ(x) x)) + (define possible-requires (filter not-false? (map find-upward require-filenames))) + (and (not (null? possible-requires)) possible-requires)) (define+provide/contract (require+provide-directory-require-files here-path #:provide [provide #t]) (coerce/path? . -> . (or/c list? void?)) + (define (put-file-in-require-form file) `(file ,(path->string file))) + (define directory-require-files (get-directory-require-files here-path)) (if directory-require-files @@ -21,7 +39,7 @@ (require ,@files-in-require-form) ,@(if provide (list `(provide (all-from-out ,@files-in-require-form))) - '()))) + null))) '(begin))) diff --git a/scribblings/formats.scrbl b/scribblings/formats.scrbl index ea98b66..70c3daf 100644 --- a/scribblings/formats.scrbl +++ b/scribblings/formats.scrbl @@ -61,7 +61,7 @@ Any value or function that is defined within the source file using @racket[defin @subsection{The @filepath{directory-require.rkt} file} -If a file called @filepath{directory-require.rkt} exists in the same directory with a source file, it's automatically imported when the source file is compiled. +If a file called @filepath{directory-require.rkt} exists in the same directory with a source file, or in a parent directory of that source file, it's automatically imported when the source file is compiled. @bold{How is this different from Racket?} In Racket, you must explicitly import files using @racket[require]. diff --git a/scribblings/tutorial-third.scrbl b/scribblings/tutorial-third.scrbl index 726beba..35d5c4f 100644 --- a/scribblings/tutorial-third.scrbl +++ b/scribblings/tutorial-third.scrbl @@ -654,9 +654,9 @@ First, using this file is not mandatory. You can always import functions and val Second, notice from the @filepath{.rkt} suffix that @filepath{directory-require.rkt} is a source file containing Racket code, not Pollen code. This is the default because while Pollen is better for text-driven source files, Racket is better for code-driven source files. Still, the choice is yours: the name of this file can be changed by resetting the @racket[world:directory-require] value. -Third, notice from the @filepath{directory-} prefix that @filepath{directory-require.rkt} is only used by Pollen source files @italic{in the same directory}. So if your project has source files nested inside a subdirectory, you'll need to explicitly create another @filepath{directory-require.rkt} there and share the functions & values as needed. +Third, the @filepath{directory-} prefix represents the minimum scope for the file, not the maximum. Pollen source files nested in subdirectories will look for a @filepath{directory-require.rkt} in their own directory first. But if they can't find it, they'll look in the parent directory, then the next parent directory, and so on. Thus, by default, a @filepath{directory-require.rkt} in the root folder of a project will apply to all the source files in the project. But when you add a new @filepath{directory-require.rkt} to a subdirectory, it will apply to all files underneath. -@margin-note{``Why not make this file visible throughout a project, rather than just a directory?'' Good idea, but I couldn't figure out how to do it without creating finicky new dependencies. If you have a better idea, I'm open to it.} +@margin-note{Though a subdirectory-specific @filepath{directory-require.rkt} will supersede the one in the enclosing directory, you can still use @racket[(require "../directory-require.rkt")] to pull in definitions from above, and @racket[provide] to propagate them into the current subdirectory. For instance, @racket[(provide (all-from-out "../directory-require.rkt"))] will re-export everything.} Let's see how this works in practice. In the same directory as @filepath{article.html.pm}, create a new @filepath{directory-require.rkt} file as follows: diff --git a/scribblings/world.scrbl b/scribblings/world.scrbl index 4e7e543..47107a5 100644 --- a/scribblings/world.scrbl +++ b/scribblings/world.scrbl @@ -68,8 +68,6 @@ Determines the default HTTP port for the project server. Initialized to @racket[ @defoverridable[meta-tag-name symbol?]{Name of the tag used to mark metas within Pollen source.} -@defoverridable[directory-require string?]{File implicitly required into every Pollen source file from its directory. Initialized to @filepath{directory-require.rkt}.} - @defoverridable[server-extras-dir string?]{Name of directory where server support files live. Initialized to @tt{server-extras}.} @defoverridable[extension-escape-char char?]{Character for escaping output-file extensions within source-file names. Initialized to @racket[#\!].} diff --git a/test/data/pathup/directory-require.rkt b/test/data/pathup/directory-require.rkt new file mode 100644 index 0000000..1febe6b --- /dev/null +++ b/test/data/pathup/directory-require.rkt @@ -0,0 +1,8 @@ +#lang racket/base +(provide (all-defined-out)) + +(define (root . xs) + `(one ,@xs)) + +(define (puppy) + "one") \ No newline at end of file diff --git a/test/data/pathup/subdir/subdir/directory-require.rkt b/test/data/pathup/subdir/subdir/directory-require.rkt new file mode 100644 index 0000000..1ea7a73 --- /dev/null +++ b/test/data/pathup/subdir/subdir/directory-require.rkt @@ -0,0 +1,8 @@ +#lang racket/base +(provide (all-defined-out)) + +(define (root . xs) + `(two ,@xs)) + +(define (puppy) + "two") \ No newline at end of file diff --git a/test/data/pathup/subdir/subdir/test-pathup-two.html.pm b/test/data/pathup/subdir/subdir/test-pathup-two.html.pm new file mode 100644 index 0000000..e217321 --- /dev/null +++ b/test/data/pathup/subdir/subdir/test-pathup-two.html.pm @@ -0,0 +1,2 @@ +#lang pollen +◊(puppy) \ No newline at end of file diff --git a/test/data/pathup/subdir/test-pathup-one.html.pm b/test/data/pathup/subdir/test-pathup-one.html.pm new file mode 100644 index 0000000..e217321 --- /dev/null +++ b/test/data/pathup/subdir/test-pathup-one.html.pm @@ -0,0 +1,2 @@ +#lang pollen +◊(puppy) \ No newline at end of file diff --git a/test/test-pathup.rkt b/test/test-pathup.rkt new file mode 100644 index 0000000..a4a7a66 --- /dev/null +++ b/test/test-pathup.rkt @@ -0,0 +1,11 @@ +#lang at-exp racket/base +(require rackunit racket/runtime-path pollen/project) + +(define-runtime-path pathup-one "data/pathup/subdir/test-pathup-one.html.pm") +(define-runtime-path dr-top "data/pathup/directory-require.rkt") +(define-runtime-path pathup-two "data/pathup/subdir/subdir/test-pathup-two.html.pm") +(define-runtime-path dr-sub "data/pathup/subdir/subdir/directory-require.rkt") + +(check-false (get-directory-require-files "test-pathup.rkt")) +(check-equal? (get-directory-require-files pathup-one) (list dr-top)) +(check-equal? (get-directory-require-files pathup-two) (list dr-sub)) \ No newline at end of file