~technomancy/fennel

allow require-as-include to work with non-literal include paths v1 SUPERSEDED

Andrey Listopadov: 1
 allow require-as-include to work with non-literal include paths

 8 files changed, 72 insertions(+), 13 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~technomancy/fennel/patches/24069/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] allow require-as-include to work with non-literal include paths Export this patch

This change allows library writers to create libraries with nested
modules, which can be required relatively to the root of the library,
making it possible to then build a self contained application, with
all library modules included into the source, regardless of how
library directory is named.
---
 src/fennel/specials.fnl  | 35 ++++++++++++++++++++++++-----------
 test/misc.fnl            | 25 +++++++++++++++++++++++--
 test/mod/foo3.fnl        |  5 +++++
 test/mod/foo4.fnl        |  5 +++++
 test/mod/foo5.fnl        |  5 +++++
 test/mod/foo6.fnl        |  5 +++++
 test/mod/nested/mod1.fnl |  1 +
 test/mod/nested/mod2.fnl |  4 ++++
 8 files changed, 72 insertions(+), 13 deletions(-)
 create mode 100644 test/mod/foo3.fnl
 create mode 100644 test/mod/foo4.fnl
 create mode 100644 test/mod/foo5.fnl
 create mode 100644 test/mod/foo6.fnl
 create mode 100644 test/mod/nested/mod1.fnl
 create mode 100644 test/mod/nested/mod2.fnl

diff --git a/src/fennel/specials.fnl b/src/fennel/specials.fnl
index 8d74bd9..4c7f4b8 100644
--- a/src/fennel/specials.fnl
+++ b/src/fennel/specials.fnl
@@ -1230,21 +1230,34 @@ Consider using import-macros instead as it is more flexible.")

(fn SPECIALS.include [ast scope parent opts]
  (compiler.assert (= (length ast) 2) "expected one argument" ast)
  (let [modexpr (. (compiler.compile1 (. ast 2) scope parent {:nval 1}) 1)]
  (let [modexpr (. (compiler.compile1 (. ast 2) scope parent {:nval 1}) 1)
        modexpr (match modexpr
                  {:type :expression}
                  (let [(ok? modname)
                        (pcall (load-code (compiler.compile (. ast 2) opts))
                               utils.root.options.module-name ast.filename)]
                    (if ok?
                        (utils.expr (string.format "%q" modname) :literal)
                        modexpr))
                  expr expr)]
    (if (or (not= modexpr.type :literal) (not= (: (. modexpr 1) :byte) 34))
        (if opts.fallback
            (opts.fallback modexpr)
            (compiler.assert false "module name must be string literal" ast))
        (let [mod ((load-code (.. "return " (. modexpr 1))))]
          (or (include-circular-fallback mod modexpr opts.fallback ast)
              (. utils.root.scope.includes mod) ; check cache
              ;; Find path to Fennel or Lua source; prefering Fennel
              (match (search-module mod)
                fennel-path (include-path ast opts fennel-path mod true)
                _ (let [lua-path (search-module mod package.path)]
                    (if lua-path (include-path ast opts lua-path mod false)
                        opts.fallback (opts.fallback modexpr)
                        (compiler.assert false (.. "module not found " mod) ast)))))))))
        (let [mod ((load-code (.. "return " (. modexpr 1))))
              oldmod utils.root.options.module-name
              _ (set utils.root.options.module-name mod)
              res (or (include-circular-fallback mod modexpr opts.fallback ast)
                      (. utils.root.scope.includes mod) ; check cache
                      ;; Find path to Fennel or Lua source; prefering Fennel
                      (match (search-module mod)
                        fennel-path (include-path ast opts fennel-path mod true)
                        _ (let [lua-path (search-module mod package.path)]
                            (if lua-path (include-path ast opts lua-path mod false)
                                opts.fallback (opts.fallback modexpr)
                                (compiler.assert false (.. "module not found " mod) ast)))))]
          (set utils.root.options.module-name oldmod)
          res))))

(doc-special :include [:module-name-literal]
             "Like require but load the target module during compilation and embed it in the
diff --git a/test/misc.fnl b/test/misc.fnl
index 33a2241..45d702c 100644
--- a/test/misc.fnl
+++ b/test/misc.fnl
@@ -26,15 +26,36 @@
  (let [expected "foo:FOO-1bar:BAR-2-BAZ-3"
        (ok out) (pcall fennel.dofile "test/mod/foo.fnl")
        (ok2 out2) (pcall fennel.dofile "test/mod/foo2.fnl"
                          {:requireAsIncluede true})]
    (l.assertTrue ok "Expected foo to run")
                          {:requireAsInclude true})
        (ok3 out3) (pcall fennel.dofile "test/mod/foo3.fnl"
                          {:requireAsInclude true})
        (ok4 out4) (pcall fennel.dofile "test/mod/foo4.fnl")
        (ok5 out5) (pcall fennel.dofile "test/mod/foo5.fnl"
                          {:requireAsInclude true}
                          :test)
        (ok6 out6) (pcall fennel.dofile "test/mod/foo6.fnl"
                          {:requireAsInclude true}
                          :test)]
    (l.assertTrue ok out)
    (l.assertTrue ok2 "Expected foo2 to run")
    (l.assertTrue ok3 "Expected foo3 to run")
    (l.assertTrue ok4 "Expected foo4 to run")
    (l.assertTrue ok5 "Expected foo5 to run")
    (l.assertTrue ok6 "Expected foo6 to run")
    (l.assertEquals (and (= :table (type out)) out.result) expected
                    (.. "Expected include to have result: " expected))
    (l.assertFalse out.quux
                   "Expected include not to leak upvalues into included modules")
    (l.assertEquals (view out) (view out2)
                    "Expected requireAsInclude to behave the same as include")
    (l.assertEquals (view out) (view out3)
                    "Expected requireAsInclude to behave the same as include when given an expression")
    (l.assertEquals (view out) (view out4)
                    "Expected include to work when given an expression")
    (l.assertEquals (view out) (view out5)
                    "Expected relative requireAsInclude to work when given a ...")
    (l.assertEquals (view out) (view out6)
                    "Expected relative requireAsInclude to work with nested modules")
    (l.assertNil _G.quux "Expected include to actually be local")
    (let [spliceOk (pcall fennel.dofile "test/mod/splice.fnl")]
      (l.assertTrue spliceOk "Expected splice to run")
diff --git a/test/mod/foo3.fnl b/test/mod/foo3.fnl
new file mode 100644
index 0000000..d955297
--- /dev/null
+++ b/test/mod/foo3.fnl
@@ -0,0 +1,5 @@
(local foo [:FOO 1])
(local quux (require (.. :test :.mod.quux)))
(local bar (require (.. :test :.mod :.bar)))
{:result (.. "foo:" (table.concat foo "-") "bar:" (table.concat bar "-"))
 : quux}
diff --git a/test/mod/foo4.fnl b/test/mod/foo4.fnl
new file mode 100644
index 0000000..c4f62e5
--- /dev/null
+++ b/test/mod/foo4.fnl
@@ -0,0 +1,5 @@
(local foo [:FOO 1])
(local quux (include (.. :test :.mod.quux)))
(local bar (include (.. :test :.mod :.bar)))
{:result (.. "foo:" (table.concat foo "-") "bar:" (table.concat bar "-"))
 : quux}
diff --git a/test/mod/foo5.fnl b/test/mod/foo5.fnl
new file mode 100644
index 0000000..2db4b56
--- /dev/null
+++ b/test/mod/foo5.fnl
@@ -0,0 +1,5 @@
(local foo [:FOO 1])
(local quux (require (.. ... :.mod.quux)))
(local bar (require (.. ... :.mod :.bar)))
{:result (.. "foo:" (table.concat foo "-") "bar:" (table.concat bar "-"))
 : quux}
diff --git a/test/mod/foo6.fnl b/test/mod/foo6.fnl
new file mode 100644
index 0000000..3f5666e
--- /dev/null
+++ b/test/mod/foo6.fnl
@@ -0,0 +1,5 @@
(local foo [:FOO 1])
(local quux (require (.. ... :.mod.quux)))
(local bar (require (.. ... :.mod.nested.mod1)))
{:result (.. "foo:" (table.concat foo "-") "bar:" (table.concat bar "-"))
 : quux}
diff --git a/test/mod/nested/mod1.fnl b/test/mod/nested/mod1.fnl
new file mode 100644
index 0000000..c5b7070
--- /dev/null
+++ b/test/mod/nested/mod1.fnl
@@ -0,0 +1 @@
(require (: (or ... "") :gsub "(nested%.).*$" "%1mod2"))
diff --git a/test/mod/nested/mod2.fnl b/test/mod/nested/mod2.fnl
new file mode 100644
index 0000000..c43df90
--- /dev/null
+++ b/test/mod/nested/mod2.fnl
@@ -0,0 +1,4 @@
(local bar [:BAR 2])
(each [_ v (ipairs (include :test.mod.baz))]
  (table.insert bar v))
bar
-- 
2.31.1