From 989ba63bf137bfcccd2f4a7c2d4ff5a486d3b2db Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Thu, 13 Jun 2024 19:43:02 +0900
Subject: [PATCH 01/10] Use fiveam instead of rove

---
 .github/workflows/test.yml | 29 +++++++++++++++++++++++++++++
 ningle-fbr-test.asd        |  7 +++++++
 ningle-fbr-tests.asd       |  9 ---------
 ningle-fbr.asd             |  2 +-
 qlfile                     |  2 +-
 qlfile.lock                |  8 ++++----
 tests/.keep                |  0
 tests/main.lisp            |  4 ----
 8 files changed, 42 insertions(+), 19 deletions(-)
 create mode 100644 .github/workflows/test.yml
 create mode 100644 ningle-fbr-test.asd
 delete mode 100644 ningle-fbr-tests.asd
 create mode 100644 tests/.keep
 delete mode 100644 tests/main.lisp

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..2c2a54a
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,29 @@
+name: 'test'
+
+on:
+  push:
+    branches:
+      - 'main'
+  pull_request:
+
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    
+    strategy:
+      matrix:
+        lisp:
+          - sbcl-bin
+          - ccl-bin
+          
+    env:
+      LISP: ${{ matrix.lisp }}
+
+    steps:
+      - uses: actions/checkout@v4
+      - uses: 40ants/setup-lisp@v4
+        with:
+          asdf-system: ningle-fbr
+      - uses: 40ants/run-tests@v2
+        with:
+          asdf-system: ningle-fbr
diff --git a/ningle-fbr-test.asd b/ningle-fbr-test.asd
new file mode 100644
index 0000000..32ff098
--- /dev/null
+++ b/ningle-fbr-test.asd
@@ -0,0 +1,7 @@
+(defsystem "ningle-fbr-test"
+  :defsystem-depends-on ("fiveam-asdf")
+  :class :package-inferred-fiveam-tester-system
+  :pathname "tests"
+  :depends-on ()
+  :test-names ()
+  :num-checks 0)
diff --git a/ningle-fbr-tests.asd b/ningle-fbr-tests.asd
deleted file mode 100644
index 8bdb5a1..0000000
--- a/ningle-fbr-tests.asd
+++ /dev/null
@@ -1,9 +0,0 @@
-(defsystem "ningle-fbr-tests"
-  :author "skyizwhite <paku@skyizwhite.dev>"
-  :license "MIT"
-  :class :package-inferred-system
-  :pathname "tests"
-  :depends-on ("rove"
-               "ningle-fbr-tests/main")
-  :perform (test-op (o c)
-                    (symbol-call :rove '#:run c)))
diff --git a/ningle-fbr.asd b/ningle-fbr.asd
index 9b2737e..083f795 100644
--- a/ningle-fbr.asd
+++ b/ningle-fbr.asd
@@ -8,4 +8,4 @@
   :class :package-inferred-system
   :pathname "src"
   :depends-on ("ningle-fbr/main")
-  :in-order-to ((test-op (test-op "ningle-fbr-tests"))))
+  :in-order-to ((test-op (test-op "ningle-fbr-test"))))
diff --git a/qlfile b/qlfile
index 087ffa1..567dbb8 100644
--- a/qlfile
+++ b/qlfile
@@ -1,3 +1,3 @@
+ql fiveam-asdf
 ql ningle
 ql cl-ppcre
-ql rove
diff --git a/qlfile.lock b/qlfile.lock
index 5634b14..57bf0bf 100644
--- a/qlfile.lock
+++ b/qlfile.lock
@@ -2,6 +2,10 @@
  (:class qlot/source/dist:source-dist
   :initargs (:distribution "https://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest)
   :version "2023-10-21"))
+("fiveam-asdf" .
+ (:class qlot/source/ql:source-ql
+  :initargs (:%version :latest)
+  :version "ql-2023-10-21"))
 ("ningle" .
  (:class qlot/source/ql:source-ql
   :initargs (:%version :latest)
@@ -10,7 +14,3 @@
  (:class qlot/source/ql:source-ql
   :initargs (:%version :latest)
   :version "ql-2023-10-21"))
-("rove" .
- (:class qlot/source/ql:source-ql
-  :initargs (:%version :latest)
-  :version "ql-2023-10-21"))
diff --git a/tests/.keep b/tests/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/main.lisp b/tests/main.lisp
deleted file mode 100644
index 06f02eb..0000000
--- a/tests/main.lisp
+++ /dev/null
@@ -1,4 +0,0 @@
-(defpackage #:ningle-fbr-tests/main
-  (:use #:cl
-        #:rove))
-(in-package #:ningle-fbr-tests/main)

From 7ab7284e88b82fdf31d55187f893e815a551ef38 Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Thu, 13 Jun 2024 21:01:22 +0900
Subject: [PATCH 02/10] Use absolute path instead

---
 src/main.lisp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main.lisp b/src/main.lisp
index 0c3632a..995b04d 100644
--- a/src/main.lisp
+++ b/src/main.lisp
@@ -47,7 +47,7 @@
     (mapcar (lambda (pathname)
               (cons (pathname->url pathname dir-namestring)
                     (pathname->package pathname system-path-namestring system-prefix)))
-            (dir->pathnames dir))))
+            (dir->pathnames dir-namestring))))
 
 (defparameter *http-request-methods*
   '(:GET :POST :PUT :DELETE :HEAD :CONNECT :OPTIONS :PATCH))

From b1dee071ff80d5b85555d23bceb51735cac21285 Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Fri, 20 Dec 2024 13:32:20 +0900
Subject: [PATCH 03/10] Refactor (#4)

* Update system info

* Update qlfile

* Organize code

* Add test

* Implement uri-mapper and package-mapper

* Change type of package from string to keyword

* Implement router
---
 ningle-fbr-test.asd |  9 +++---
 ningle-fbr.asd      |  5 ++--
 qlfile              |  4 ++-
 qlfile.lock         | 16 ++++++++---
 src/main.lisp       | 68 ++-------------------------------------------
 src/router.lisp     | 67 ++++++++++++++++++++++++++++++++++++++++++++
 tests/.keep         |  0
 tests/router.lisp   | 34 +++++++++++++++++++++++
 8 files changed, 126 insertions(+), 77 deletions(-)
 create mode 100644 src/router.lisp
 delete mode 100644 tests/.keep
 create mode 100644 tests/router.lisp

diff --git a/ningle-fbr-test.asd b/ningle-fbr-test.asd
index 32ff098..47b744e 100644
--- a/ningle-fbr-test.asd
+++ b/ningle-fbr-test.asd
@@ -1,7 +1,6 @@
 (defsystem "ningle-fbr-test"
-  :defsystem-depends-on ("fiveam-asdf")
-  :class :package-inferred-fiveam-tester-system
+  :class :package-inferred-system
   :pathname "tests"
-  :depends-on ()
-  :test-names ()
-  :num-checks 0)
+  :depends-on ("rove"
+               "ningle-fbr-test/router")
+  :perform (test-op (o c) (symbol-call :rove :run c :style :dot)))
diff --git a/ningle-fbr.asd b/ningle-fbr.asd
index 083f795..a567398 100644
--- a/ningle-fbr.asd
+++ b/ningle-fbr.asd
@@ -1,9 +1,10 @@
 (defsystem "ningle-fbr"
   :version "0.1.0"
-  :description "Plugin for ningle to enable file-based routing"
+  :description "File-based router for Ningle"
   :long-description #.(uiop:read-file-string
                        (uiop:subpathname *load-pathname* "README.md"))
-  :author "skyizwhite <paku@skyizwhite.dev>"
+  :author "skyizwhite"
+  :maintainer "skyizwhite <paku@skyizwhite.dev>"
   :license "MIT"
   :class :package-inferred-system
   :pathname "src"
diff --git a/qlfile b/qlfile
index 567dbb8..048c828 100644
--- a/qlfile
+++ b/qlfile
@@ -1,3 +1,5 @@
-ql fiveam-asdf
 ql ningle
 ql cl-ppcre
+ql alexandria
+github rove fukamachi/rove
+github dissect shinmera/dissect
diff --git a/qlfile.lock b/qlfile.lock
index 57bf0bf..56722c8 100644
--- a/qlfile.lock
+++ b/qlfile.lock
@@ -2,10 +2,6 @@
  (:class qlot/source/dist:source-dist
   :initargs (:distribution "https://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest)
   :version "2023-10-21"))
-("fiveam-asdf" .
- (:class qlot/source/ql:source-ql
-  :initargs (:%version :latest)
-  :version "ql-2023-10-21"))
 ("ningle" .
  (:class qlot/source/ql:source-ql
   :initargs (:%version :latest)
@@ -14,3 +10,15 @@
  (:class qlot/source/ql:source-ql
   :initargs (:%version :latest)
   :version "ql-2023-10-21"))
+("alexandria" .
+ (:class qlot/source/ql:source-ql
+  :initargs (:%version :latest)
+  :version "ql-2024-10-12"))
+("rove" .
+ (:class qlot/source/github:source-github
+  :initargs (:repos "fukamachi/rove" :ref nil :branch nil :tag nil)
+  :version "github-cacea7331c10fe9d8398d104b2dfd579bf7ea353"))
+("dissect" .
+ (:class qlot/source/github:source-github
+  :initargs (:repos "shinmera/dissect" :ref nil :branch nil :tag nil)
+  :version "github-a70cabcd748cf7c041196efd711e2dcca2bbbb2c"))
diff --git a/src/main.lisp b/src/main.lisp
index 995b04d..8236432 100644
--- a/src/main.lisp
+++ b/src/main.lisp
@@ -1,68 +1,6 @@
 (uiop:define-package :ningle-fbr
   (:nicknames #:ningle-fbr/main)
-  (:use #:cl)
-  (:import-from #:cl-ppcre)
-  (:import-from #:ningle)
-  (:export #:assign-routes))
+  (:use #:cl
+        #:ningle-fbr/router)
+  (:export #:set-routes))
 (in-package :ningle-fbr)
-
-(defun remove-file-type (namestr)
-  (cl-ppcre:regex-replace ".lisp" namestr ""))
-
-(defun remove-index (url)
-  (if (string= url "/index")
-      "/"
-      (cl-ppcre:regex-replace "/index" url "")))
-
-(defun replace-dynamic-annotation (url)
-  (cl-ppcre:regex-replace "=" url ":"))
-
-(defun format-url (url)
-  (replace-dynamic-annotation (remove-index url)))
-
-(defun pathname->url (pathname dir-namestring)
-  (format-url
-   (cl-ppcre:regex-replace dir-namestring
-                           (remove-file-type (namestring pathname))
-                           "")))
-
-(defun pathname->package (pathname system-path-namestring system-prefix)
-  (string-upcase
-   (cl-ppcre:regex-replace system-path-namestring
-                           (remove-file-type (namestring pathname))
-                           system-prefix)))
-
-(defun dir->pathnames (dir)
-  (directory (concatenate 'string
-                          dir
-                          "/**/*.lisp")))
-
-(defun dir->urls-and-packages (dir system)
-  (let ((dir-namestring (namestring
-                         (asdf:system-relative-pathname system dir)))
-        (system-path-namestring (namestring 
-                                 (asdf/component:component-relative-pathname
-                                  (asdf/find-system:find-system system))))
-        (system-prefix (concatenate 'string system "/")))
-    (mapcar (lambda (pathname)
-              (cons (pathname->url pathname dir-namestring)
-                    (pathname->package pathname system-path-namestring system-prefix)))
-            (dir->pathnames dir-namestring))))
-
-(defparameter *http-request-methods*
-  '(:GET :POST :PUT :DELETE :HEAD :CONNECT :OPTIONS :PATCH))
-
-(defun assign-routes (app &key directory system)
-  (loop
-    :for (url . pkg) :in (dir->urls-and-packages directory system)
-    :do (ql:quickload pkg)
-        (if (string= url "/not-found")
-            (let ((handler (find-symbol "HANDLE-NOT-FOUND" pkg)))
-              (defmethod ningle:not-found ((app ningle:app))
-                (funcall handler))))
-        (loop
-          :for method :in *http-request-methods*
-          :do (let ((handler (find-symbol (concatenate 'string "HANDLE-" (string method))
-                                          pkg)))
-                (when handler
-                  (setf (ningle:route app url :method method) handler))))))
diff --git a/src/router.lisp b/src/router.lisp
new file mode 100644
index 0000000..3c75566
--- /dev/null
+++ b/src/router.lisp
@@ -0,0 +1,67 @@
+(defpackage #:ningle-fbr/router
+  (:use #:cl)
+  (:import-from #:alexandria
+                #:make-keyword)
+  (:import-from #:cl-ppcre
+                #:quote-meta-chars
+                #:regex-replace
+                #:regex-replace-all)
+  (:import-from #:ningle)
+  (:export #:pathname->path
+           #:path->uri
+           #:path-package
+           #:set-routes))
+(in-package #:ningle-fbr/router)
+
+(defun pathname->path (pathname target-dir-pathname)
+  (let* ((full (namestring pathname))
+         (prefix (quote-meta-chars (namestring target-dir-pathname))))
+    (regex-replace (format nil "^~A(.*?).lisp$" prefix) full "/\\1")))
+
+(defun detect-paths (system target-dir-path)
+  (let ((target-dir-pathname
+          (merge-pathnames (concatenate 'string
+                                        target-dir-path
+                                        "/")
+                           (asdf:component-pathname (asdf:find-system system)))))
+    (mapcar (lambda (pathname)
+              (pathname->path pathname target-dir-pathname))
+            (directory (merge-pathnames "**/*.lisp" target-dir-pathname)))))
+
+(defun remove-index (path)
+  (if (string= path "/index")
+      "/"
+      (regex-replace "/index$" path "")))
+
+(defun bracket->colon (path)
+  (regex-replace-all "\\[(.*?)\\]" path ":\\1"))
+
+(defun path->uri (path)
+  (bracket->colon (remove-index path)))
+
+(defun path->package (path system target-dir-path)
+  (make-keyword (string-upcase (concatenate 'string
+                                            (string system)
+                                            "/"
+                                            target-dir-path
+                                            path))))
+
+(defparameter *http-request-methods*
+  '(:GET :POST :PUT :DELETE :HEAD :CONNECT :OPTIONS :PATCH))
+
+(defmethod set-routes ((app ningle:app) &key system target-dir-path)
+  (loop
+    :for path :in (detect-paths system target-dir-path)
+    :for uri := (path->uri path)
+    :for pkg := (path->package path system target-dir-path)
+    :do (ql:quickload pkg) 
+        (if (string= uri "/not-found")
+            (let ((handler (find-symbol "HANDLE-NOT-FOUND" pkg)))
+              (defmethod ningle:not-found ((app ningle:app))
+                (funcall handler))))
+        (loop
+          :for method :in *http-request-methods*
+          :do (let ((handler (find-symbol (concatenate 'string "HANDLE-" (string method))
+                                          pkg)))
+                (when handler
+                  (setf (ningle:route app uri :method method) handler))))))
diff --git a/tests/.keep b/tests/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/router.lisp b/tests/router.lisp
new file mode 100644
index 0000000..0196212
--- /dev/null
+++ b/tests/router.lisp
@@ -0,0 +1,34 @@
+(defpackage #:ningle-fbr-test/router
+  (:use #:cl
+        #:rove)
+  (:import-from #:ningle-fbr/router
+                #:pathname->path
+                #:path->uri
+                #:path->package))
+(in-package #:ningle-fbr-test/router)
+
+(deftest router-test
+  (testing "pathname->path"
+    (ok (string= (pathname->path #P"/home/app/src/routes/foo.lisp"
+                                 #P"/home/app/src/routes/")
+                 "/foo"))))
+
+(deftest uri-test
+  (testing "normal path"
+    (ok (string= (path->uri "/foo") "/foo"))
+    (ok (string= (path->uri "/foo/bar") "/foo/bar")))
+  
+  (testing "index path"
+    (ok (string= (path->uri "/index") "/"))
+    (ok (string= (path->uri "/nested/index") "/nested")))
+  
+  (testing "dynamic path"
+    (ok (string= (path->uri "/user/[id]") "/user/:id"))
+    (ok (string= (path->uri "/location/[country]/[city]") "/location/:country/:city" ))))
+
+(deftest package-test
+  (testing "normal case"
+    (ok (eq (path->package "/foo" :app "routes")
+            :app/routes/foo))
+    (ok (eq (path->package "/foo" :app "somedir/routes")
+            :app/somedir/routes/foo))))

From 09c5a126be62bb84d0619f7bc200f809df9633b7 Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Fri, 20 Dec 2024 15:09:37 +0900
Subject: [PATCH 04/10] Update README

---
 README.md | 48 ++++++++++++++++++++++++++----------------------
 1 file changed, 26 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md
index ee74370..fd8622c 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,22 @@
-# ningle-fbr (WIP)
+# ningle-fbr
 
-A utility for [ningle](https://github.com/fukamachi/ningle) and [jingle](https://github.com/dnaeon/cl-jingle) to enable file-based routing.
+A file-based router for [ningle](https://github.com/fukamachi/ningle).
+
+## Warning
+
+This software is still in ALPHA quality. The APIs are likely to change.
+
+Please check the [release notes](https://github.com/skyizwhite/ningle-fbr/releases) for updates.
 
 ## What is File-Based Routing?
 
-File-based routing is a concept commonly used in modern web frameworks such as [Next.js](https://nextjs.org/). Instead of explicitly defining routes through configuration or code, the framework automatically sets up routes based on the file hierarchy of a specific directory (usually the "pages" or "routes" directory).
+File-based routing is a concept widely used in modern web frameworks like [Next.js](https://nextjs.org/). Instead of explicitly defining routes in code or configuration, routes are automatically generated based on the file and directory structure within a designated folder (often called "pages" or "routes").
 
 ## Usage
 
-To use ningle-fbr, you need to use [package-inferred-system](https://asdf.common-lisp.dev/asdf/The-package_002dinferred_002dsystem-extension.html).
+To use ningle-fbr, you need to set up your project based on the [package-inferred-system](https://asdf.common-lisp.dev/asdf/The-package_002dinferred_002dsystem-extension.html).
 
-`/example.asd`
+`/example.asd`:
 ```lisp
 (defsystem "example"
   :class :package-inferred-system
@@ -18,33 +24,31 @@ To use ningle-fbr, you need to use [package-inferred-system](https://asdf.common
   :depends-on ("example/app"))
 ```
 
-`/src/app.lisp`
+`/src/app.lisp`:
 ```lisp
 (defpackage #:example
   (:nicknames #:example/app)
   (:use #:cl)
   (:import-from #:ningle)
   (:import-from #:ningle-fbr
-                #:assign-routes))
+                #:set-routes))
 (in-package #:example/app)
 
 (defparameter *app* (make-instance 'ningle:<app>))
 
-(assign-routes *app*
-               :directory "src/routes"
-               :system "example")
+(set-routes *app* :system :example :target-dir-path "routes")
 ```
 
 ### Static Routing
 
-`/src/routes/index.lisp` → `/`
+Routes are generated automatically from packages under `:example/routes`:
 
-`/src/routes/hello.lisp` → `/hello`
-
-`/src/routes/users/index.lisp` → `/users`
-
-`/src/routes/nested/page.lisp` → `/nested/page`
+- `:example/routes/index` → `/`
+- `:example/routes/hello` → `/hello`
+- `:example/routes/users/index` → `/users`
+- `:example/routes/nested/page` → `/nested/page`
 
+`/src/routes/index.lisp` example:
 ```lisp
 (defpackage #:example/routes/index
   (:use #:cl)
@@ -69,22 +73,22 @@ To use ningle-fbr, you need to use [package-inferred-system](https://asdf.common
 
 ### Dynamic Routing
 
-A file or directory name prefixed with '=' indicates a dynamic path.
+Dynamic routes use square brackets to indicate parameters. For example:
 
-In the example below, the parameter `id` can be obtained from the handler's params.
+`/src/routes/user/[id].lisp` → `/user/[id]`
 
-`/src/routes/user/=id.lisp` → `/user/:id`
+In the handlers, you can access the value of `id` through the `params` argument.
 
 ### Not Found Error
 
-`not-found.lisp` is a special file to handle 404 errors. Implement the `handle-not-found` function and export it.
+`:example/routes/not-found` is a special package for handling 404 errors. Implement and export `handle-not-found`:
 
 ```lisp
 (defpackage #:example/routes/not-found
   (:use #:cl)
   (:export #:handle-not-found))
 (in-package #:example/routes/not-found)
-  
+
 (defun handle-not-found ()
   ...)
 ```
@@ -93,4 +97,4 @@ In the example below, the parameter `id` can be obtained from the handler's para
 
 Licensed under the MIT License.
 
-© 2024, skyizwhite.
+© 2024, skyizwhite.
\ No newline at end of file

From d20dbe264911b7dfd6e6fade899db9b09bd2dd32 Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Fri, 20 Dec 2024 15:15:04 +0900
Subject: [PATCH 05/10] Fix

---
 README.md         | 4 ++--
 src/router.lisp   | 2 +-
 tests/router.lisp | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index fd8622c..43e8ae3 100644
--- a/README.md
+++ b/README.md
@@ -73,9 +73,9 @@ Routes are generated automatically from packages under `:example/routes`:
 
 ### Dynamic Routing
 
-Dynamic routes use square brackets to indicate parameters. For example:
+Dynamic routes use `< >` to indicate parameters. For example:
 
-`/src/routes/user/[id].lisp` → `/user/[id]`
+`/src/routes/user/<id>.lisp` → `/user/:id`
 
 In the handlers, you can access the value of `id` through the `params` argument.
 
diff --git a/src/router.lisp b/src/router.lisp
index 3c75566..9f974c3 100644
--- a/src/router.lisp
+++ b/src/router.lisp
@@ -34,7 +34,7 @@
       (regex-replace "/index$" path "")))
 
 (defun bracket->colon (path)
-  (regex-replace-all "\\[(.*?)\\]" path ":\\1"))
+  (regex-replace-all "<(.*?)>" path ":\\1"))
 
 (defun path->uri (path)
   (bracket->colon (remove-index path)))
diff --git a/tests/router.lisp b/tests/router.lisp
index 0196212..564c06d 100644
--- a/tests/router.lisp
+++ b/tests/router.lisp
@@ -23,8 +23,8 @@
     (ok (string= (path->uri "/nested/index") "/nested")))
   
   (testing "dynamic path"
-    (ok (string= (path->uri "/user/[id]") "/user/:id"))
-    (ok (string= (path->uri "/location/[country]/[city]") "/location/:country/:city" ))))
+    (ok (string= (path->uri "/user/<id>") "/user/:id"))
+    (ok (string= (path->uri "/location/<country>/<city>") "/location/:country/:city" ))))
 
 (deftest package-test
   (testing "normal case"

From 234b2e50f38e04836c1f6d5123a1eec317149542 Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Fri, 20 Dec 2024 15:30:29 +0900
Subject: [PATCH 06/10] Update README

---
 README.md | 57 +++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 39 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md
index 43e8ae3..b01bb22 100644
--- a/README.md
+++ b/README.md
@@ -10,13 +10,27 @@ Please check the [release notes](https://github.com/skyizwhite/ningle-fbr/releas
 
 ## What is File-Based Routing?
 
-File-based routing is a concept widely used in modern web frameworks like [Next.js](https://nextjs.org/). Instead of explicitly defining routes in code or configuration, routes are automatically generated based on the file and directory structure within a designated folder (often called "pages" or "routes").
+File-based routing automatically generates URL routes based on a project’s file and directory structure. Instead of manually configuring routes in a separate routing file, each file in a designated directory (e.g., `pages` or `routes`) becomes a route. This simplifies development and maintenance since adding, removing, or renaming a route is often just a matter of modifying a file’s name or location.
 
 ## Usage
 
-To use ningle-fbr, you need to set up your project based on the [package-inferred-system](https://asdf.common-lisp.dev/asdf/The-package_002dinferred_002dsystem-extension.html).
+To use ningle-fbr, set up your project in accordance with the [package-inferred-system](https://asdf.common-lisp.dev/asdf/The-package_002dinferred_002dsystem-extension.html) conventions.
 
-`/example.asd`:
+**Example directory structure**:  
+```
+example.asd
+src/
+  app.lisp
+  routes/
+    index.lisp
+    hello.lisp
+    users/
+      index.lisp
+    nested/
+      page.lisp
+```
+
+**`example.asd`**:
 ```lisp
 (defsystem "example"
   :class :package-inferred-system
@@ -24,14 +38,13 @@ To use ningle-fbr, you need to set up your project based on the [package-inferre
   :depends-on ("example/app"))
 ```
 
-`/src/app.lisp`:
+**`/src/app.lisp`**:
 ```lisp
 (defpackage #:example
   (:nicknames #:example/app)
   (:use #:cl)
   (:import-from #:ningle)
-  (:import-from #:ningle-fbr
-                #:set-routes))
+  (:import-from #:ningle-fbr #:set-routes))
 (in-package #:example/app)
 
 (defparameter *app* (make-instance 'ningle:<app>))
@@ -41,14 +54,14 @@ To use ningle-fbr, you need to set up your project based on the [package-inferre
 
 ### Static Routing
 
-Routes are generated automatically from packages under `:example/routes`:
+Routes are derived from packages under `:example/routes`. The package’s name corresponds directly to a URL path:
 
 - `:example/routes/index` → `/`
 - `:example/routes/hello` → `/hello`
 - `:example/routes/users/index` → `/users`
 - `:example/routes/nested/page` → `/nested/page`
 
-`/src/routes/index.lisp` example:
+**`/src/routes/index.lisp`**:
 ```lisp
 (defpackage #:example/routes/index
   (:use #:cl)
@@ -59,29 +72,36 @@ Routes are generated automatically from packages under `:example/routes`:
 (in-package #:example/routes/index)
 
 (defun handle-get (params)
-  ...)
+  ;; implement GET logic here
+  )
 
 (defun handle-post (params)
-  ...)
+  ;; implement POST logic here
+  )
 
 (defun handle-put (params)
-  ...)
+  ;; implement PUT logic here
+  )
 
 (defun handle-delete (params)
-  ...)
+  ;; implement DELETE logic here
+  )
 ```
 
+Handlers are chosen based on the HTTP method. If `handle-get` is exported, it will be called for `GET` requests on `/`.
+
 ### Dynamic Routing
 
-Dynamic routes use `< >` to indicate parameters. For example:
+To define dynamic routes, use `<>` in the file name to indicate URL parameters.
 
-`/src/routes/user/<id>.lisp` → `/user/:id`
+For example:
+`:example/routes/user/<id>` → `/user/:id`
 
-In the handlers, you can access the value of `id` through the `params` argument.
+If a request comes in at `/user/123`, `params` will include `:id "123"`.
 
-### Not Found Error
+### 404 Handling
 
-`:example/routes/not-found` is a special package for handling 404 errors. Implement and export `handle-not-found`:
+To handle 404 (Not Found) error, create a special package named `:example/routes/not-found` and define `handle-not-found`:
 
 ```lisp
 (defpackage #:example/routes/not-found
@@ -90,7 +110,8 @@ In the handlers, you can access the value of `id` through the `params` argument.
 (in-package #:example/routes/not-found)
 
 (defun handle-not-found ()
-  ...)
+  ;; Implement custom 404 logic here
+  )
 ```
 
 ## License

From 7641ec5f6190a5b31540b874c3261c302dc3391b Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Sun, 22 Dec 2024 18:33:49 +0900
Subject: [PATCH 07/10] Add test for set-routes

---
 README.md                     |  1 +
 qlfile                        |  1 +
 qlfile.lock                   |  4 +++
 tests/router.lisp             | 67 ++++++++++++++++++++++++++++++-----
 tests/routes/hello.lisp       |  8 +++++
 tests/routes/index.lisp       |  8 +++++
 tests/routes/nested/page.lisp |  8 +++++
 tests/routes/not-found.lisp   | 11 ++++++
 tests/routes/users/<id>.lisp  |  8 +++++
 tests/routes/users/index.lisp |  8 +++++
 10 files changed, 115 insertions(+), 9 deletions(-)
 create mode 100644 tests/routes/hello.lisp
 create mode 100644 tests/routes/index.lisp
 create mode 100644 tests/routes/nested/page.lisp
 create mode 100644 tests/routes/not-found.lisp
 create mode 100644 tests/routes/users/<id>.lisp
 create mode 100644 tests/routes/users/index.lisp

diff --git a/README.md b/README.md
index b01bb22..ecb0a03 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ src/
     hello.lisp
     users/
       index.lisp
+      <id>.lisp
     nested/
       page.lisp
 ```
diff --git a/qlfile b/qlfile
index 048c828..548652c 100644
--- a/qlfile
+++ b/qlfile
@@ -1,4 +1,5 @@
 ql ningle
+ql lack
 ql cl-ppcre
 ql alexandria
 github rove fukamachi/rove
diff --git a/qlfile.lock b/qlfile.lock
index 56722c8..003a88d 100644
--- a/qlfile.lock
+++ b/qlfile.lock
@@ -6,6 +6,10 @@
  (:class qlot/source/ql:source-ql
   :initargs (:%version :latest)
   :version "ql-2023-10-21"))
+("lack" .
+ (:class qlot/source/ql:source-ql
+  :initargs (:%version :latest)
+  :version "ql-2024-10-12"))
 ("cl-ppcre" .
  (:class qlot/source/ql:source-ql
   :initargs (:%version :latest)
diff --git a/tests/router.lisp b/tests/router.lisp
index 564c06d..03724a0 100644
--- a/tests/router.lisp
+++ b/tests/router.lisp
@@ -1,18 +1,18 @@
 (defpackage #:ningle-fbr-test/router
   (:use #:cl
         #:rove)
+  (:import-from #:ningle)
+  (:import-from #:lack)
+  (:import-from #:lack/test
+                #:testing-app
+                #:request)
   (:import-from #:ningle-fbr/router
-                #:pathname->path
                 #:path->uri
-                #:path->package))
+                #:path->package
+                #:pathname->path
+                #:set-routes))
 (in-package #:ningle-fbr-test/router)
 
-(deftest router-test
-  (testing "pathname->path"
-    (ok (string= (pathname->path #P"/home/app/src/routes/foo.lisp"
-                                 #P"/home/app/src/routes/")
-                 "/foo"))))
-
 (deftest uri-test
   (testing "normal path"
     (ok (string= (path->uri "/foo") "/foo"))
@@ -24,7 +24,7 @@
   
   (testing "dynamic path"
     (ok (string= (path->uri "/user/<id>") "/user/:id"))
-    (ok (string= (path->uri "/location/<country>/<city>") "/location/:country/:city" ))))
+    (ok (string= (path->uri "/location/<country>/<city>") "/location/:country/:city"))))
 
 (deftest package-test
   (testing "normal case"
@@ -32,3 +32,52 @@
             :app/routes/foo))
     (ok (eq (path->package "/foo" :app "somedir/routes")
             :app/somedir/routes/foo))))
+
+(deftest router-test
+  (testing "pathname->path"
+    (ok (string= (pathname->path #P"/home/app/src/routes/foo.lisp"
+                                 #P"/home/app/src/routes/")
+                 "/foo")))
+  
+  (testing "set-routes"
+    (testing-app (let ((app (make-instance 'ningle:app)))
+                   (set-routes app
+                               :system :ningle-fbr-test
+                               :target-dir-path "routes")
+                   (lack:builder app))
+      (multiple-value-bind (body status headers)
+          (request "/")
+        (declare (ignore headers))
+        (ok (string= body "ok"))
+        (ok (eql status 200)))
+
+      (multiple-value-bind (body status headers)
+          (request "/hello")
+        (declare (ignore headers))
+        (ok (string= body "ok"))
+        (ok (eql status 200)))
+      
+      (multiple-value-bind (body status headers)
+          (request "/nested/page")
+        (declare (ignore headers))
+        (ok (string= body "ok"))
+        (ok (eql status 200)))
+      
+      (multiple-value-bind (body status headers)
+          (request "/users")
+        (declare (ignore headers))
+        (ok (string= body "ok"))
+        (ok (eql status 200)))
+      
+      (multiple-value-bind (body status headers)
+          (request "/users/bob")
+        (declare (ignore headers))
+        (ok (string= body "bob"))
+        (ok (eql status 200)))
+      
+      (multiple-value-bind (body status headers)
+          (request "/missing")
+        (declare (ignore headers))
+        (ok (string= body "Not Found"))
+        (ok (eql status 404))))))
+
diff --git a/tests/routes/hello.lisp b/tests/routes/hello.lisp
new file mode 100644
index 0000000..18beefa
--- /dev/null
+++ b/tests/routes/hello.lisp
@@ -0,0 +1,8 @@
+(defpackage #:ningle-fbr-test/routes/hello
+  (:use #:cl)
+  (:export #:handle-get))
+(in-package #:ningle-fbr-test/routes/hello)
+
+(defun handle-get (params)
+  (declare (ignore params))
+  "ok")
diff --git a/tests/routes/index.lisp b/tests/routes/index.lisp
new file mode 100644
index 0000000..dab5d79
--- /dev/null
+++ b/tests/routes/index.lisp
@@ -0,0 +1,8 @@
+(defpackage #:ningle-fbr-test/routes/index
+  (:use #:cl)
+  (:export #:handle-get))
+(in-package #:ningle-fbr-test/routes/index)
+
+(defun handle-get (params)
+  (declare (ignore params))
+  "ok")
diff --git a/tests/routes/nested/page.lisp b/tests/routes/nested/page.lisp
new file mode 100644
index 0000000..afc9f62
--- /dev/null
+++ b/tests/routes/nested/page.lisp
@@ -0,0 +1,8 @@
+(defpackage #:ningle-fbr-test/routes/nested/page
+  (:use #:cl)
+  (:export #:handle-get))
+(in-package #:ningle-fbr-test/routes/nested/page)
+
+(defun handle-get (params)
+  (declare (ignore params))
+  "ok")
diff --git a/tests/routes/not-found.lisp b/tests/routes/not-found.lisp
new file mode 100644
index 0000000..5392fd8
--- /dev/null
+++ b/tests/routes/not-found.lisp
@@ -0,0 +1,11 @@
+(defpackage #:ningle-fbr-test/routes/not-found
+  (:use #:cl)
+  (:import-from #:lack/response)
+  (:import-from #:ningle)
+  (:export #:handle-not-found))
+(in-package #:ningle-fbr-test/routes/not-found)
+
+(defun handle-not-found ()
+  (setf (lack/response:response-status ningle:*response*)
+        404)
+  "Not Found")
diff --git a/tests/routes/users/<id>.lisp b/tests/routes/users/<id>.lisp
new file mode 100644
index 0000000..0eb385f
--- /dev/null
+++ b/tests/routes/users/<id>.lisp
@@ -0,0 +1,8 @@
+(defpackage #:ningle-fbr-test/routes/users/<id>
+  (:use #:cl)
+  (:export #:handle-get))
+(in-package #:ningle-fbr-test/routes/users/<id>)
+
+(defun handle-get (params)
+  (let ((id (cdr (assoc :id params))))
+    id))
diff --git a/tests/routes/users/index.lisp b/tests/routes/users/index.lisp
new file mode 100644
index 0000000..5473c9b
--- /dev/null
+++ b/tests/routes/users/index.lisp
@@ -0,0 +1,8 @@
+(defpackage #:ningle-fbr-test/routes/users/index
+  (:use #:cl)
+  (:export #:handle-get))
+(in-package #:ningle-fbr-test/routes/users/index)
+
+(defun handle-get (params)
+  (declare (ignore params))
+  "ok")

From 1151e4adacffa6b0be59fba136520a430cbdc704 Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Wed, 25 Dec 2024 02:47:46 +0900
Subject: [PATCH 08/10] Update README

---
 README.md | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index ecb0a03..0b2d284 100644
--- a/README.md
+++ b/README.md
@@ -4,19 +4,19 @@ A file-based router for [ningle](https://github.com/fukamachi/ningle).
 
 ## Warning
 
-This software is still in ALPHA quality. The APIs are likely to change.
+This software is currently in ALPHA stage. Its APIs are subject to change.  
 
-Please check the [release notes](https://github.com/skyizwhite/ningle-fbr/releases) for updates.
+Check the [release notes](https://github.com/skyizwhite/ningle-fbr/releases) for the latest updates.
 
 ## What is File-Based Routing?
 
-File-based routing automatically generates URL routes based on a project’s file and directory structure. Instead of manually configuring routes in a separate routing file, each file in a designated directory (e.g., `pages` or `routes`) becomes a route. This simplifies development and maintenance since adding, removing, or renaming a route is often just a matter of modifying a file’s name or location.
+File-based routing automatically creates URL routes based on a project’s file and directory structure. Instead of manually configuring routes in a separate routing file, each file in a designated directory (e.g., `pages` or `routes`) becomes a route. This simplifies development and maintenance since adding, removing, or renaming a route is often just a matter of modifying a file’s name or location.
 
 ## Usage
 
-To use ningle-fbr, set up your project in accordance with the [package-inferred-system](https://asdf.common-lisp.dev/asdf/The-package_002dinferred_002dsystem-extension.html) conventions.
+To use ningle-fbr, set up your project using the [package-inferred-system](https://asdf.common-lisp.dev/asdf/The-package_002dinferred_002dsystem-extension.html) style.
 
-**Example directory structure**:  
+**Example directory structure**:
 ```
 example.asd
 src/
@@ -55,7 +55,7 @@ src/
 
 ### Static Routing
 
-Routes are derived from packages under `:example/routes`. The package’s name corresponds directly to a URL path:
+Routes are determined by packages located under `:example/routes`. The package’s name corresponds directly to a URL path:
 
 - `:example/routes/index` → `/`
 - `:example/routes/hello` → `/hello`
@@ -73,19 +73,19 @@ Routes are derived from packages under `:example/routes`. The package’s name c
 (in-package #:example/routes/index)
 
 (defun handle-get (params)
-  ;; implement GET logic here
+  ;; Implement GET logic here
   )
 
 (defun handle-post (params)
-  ;; implement POST logic here
+  ;; Implement POST logic here
   )
 
 (defun handle-put (params)
-  ;; implement PUT logic here
+  ;; Implement PUT logic here
   )
 
 (defun handle-delete (params)
-  ;; implement DELETE logic here
+  ;; Implement DELETE logic here
   )
 ```
 
@@ -102,7 +102,7 @@ If a request comes in at `/user/123`, `params` will include `:id "123"`.
 
 ### 404 Handling
 
-To handle 404 (Not Found) error, create a special package named `:example/routes/not-found` and define `handle-not-found`:
+To handle 404 (Not Found) errors, create a special package named `:example/routes/not-found` and define `handle-not-found`:
 
 ```lisp
 (defpackage #:example/routes/not-found

From 90ac8ee8f5258d18d2a02eb672bc4e9090dc44cc Mon Sep 17 00:00:00 2001
From: Anthony Green <green@moxielogic.com>
Date: Sat, 28 Dec 2024 07:41:05 -0700
Subject: [PATCH 09/10] Remove dependency on quicklisp (#5)

---
 src/router.lisp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/router.lisp b/src/router.lisp
index 9f974c3..fc780a9 100644
--- a/src/router.lisp
+++ b/src/router.lisp
@@ -7,6 +7,7 @@
                 #:regex-replace
                 #:regex-replace-all)
   (:import-from #:ningle)
+	(:import-from #:trivial-system-loader)
   (:export #:pathname->path
            #:path->uri
            #:path-package
@@ -54,7 +55,7 @@
     :for path :in (detect-paths system target-dir-path)
     :for uri := (path->uri path)
     :for pkg := (path->package path system target-dir-path)
-    :do (ql:quickload pkg) 
+    :do (load-system pkg)
         (if (string= uri "/not-found")
             (let ((handler (find-symbol "HANDLE-NOT-FOUND" pkg)))
               (defmethod ningle:not-found ((app ningle:app))

From 6cdf49965b2857e08454d29381cd96650e8f2614 Mon Sep 17 00:00:00 2001
From: paku <paku@skyizwhite.dev>
Date: Sat, 28 Dec 2024 23:44:37 +0900
Subject: [PATCH 10/10] Add trivial-system-loader to qlot deps

---
 qlfile          | 1 +
 qlfile.lock     | 4 ++++
 src/router.lisp | 3 ++-
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/qlfile b/qlfile
index 548652c..88e3eca 100644
--- a/qlfile
+++ b/qlfile
@@ -2,5 +2,6 @@ ql ningle
 ql lack
 ql cl-ppcre
 ql alexandria
+ql trivial-system-loader
 github rove fukamachi/rove
 github dissect shinmera/dissect
diff --git a/qlfile.lock b/qlfile.lock
index 003a88d..251711d 100644
--- a/qlfile.lock
+++ b/qlfile.lock
@@ -18,6 +18,10 @@
  (:class qlot/source/ql:source-ql
   :initargs (:%version :latest)
   :version "ql-2024-10-12"))
+("trivial-system-loader" .
+ (:class qlot/source/ql:source-ql
+  :initargs (:%version :latest)
+  :version "ql-2024-10-12"))
 ("rove" .
  (:class qlot/source/github:source-github
   :initargs (:repos "fukamachi/rove" :ref nil :branch nil :tag nil)
diff --git a/src/router.lisp b/src/router.lisp
index fc780a9..de26076 100644
--- a/src/router.lisp
+++ b/src/router.lisp
@@ -7,7 +7,8 @@
                 #:regex-replace
                 #:regex-replace-all)
   (:import-from #:ningle)
-	(:import-from #:trivial-system-loader)
+  (:import-from #:trivial-system-loader
+                #:load-system)
   (:export #:pathname->path
            #:path->uri
            #:path-package