diff --git a/full/doc.lisp b/full/doc.lisp
index f4a379c..6bc9b99 100644
--- a/full/doc.lisp
+++ b/full/doc.lisp
@@ -114,6 +114,8 @@
"
+ |
+
 |
 |
diff --git a/full/github.lisp b/full/github.lisp
index 82bc53a..f845919 100644
--- a/full/github.lisp
+++ b/full/github.lisp
@@ -9,8 +9,12 @@
#:reference-object)
(:import-from #:40ants-doc/source-api)
(:import-from #:cl-ppcre)
- (:import-from #:str)
+ (:import-from #:str
+ #:trim
+ #:split)
(:import-from #:alexandria
+ #:read-file-into-string
+ #:length=
#:starts-with-subseq)
(:import-from #:40ants-doc/reference-api
#:*source-uri-fn*
@@ -108,23 +112,60 @@
for ASDF system~% ~A. Links to github will not be generated."
(asdf:component-name (asdf:registered-system asdf-system))))))
+
+(defun read-git-dir-link (pathname)
+ "When system is cloned as a submodule, then instead of .git/ directory there is a file .git
+ with a link like this:
+
+ ```
+ gitdir: ../../.git/modules/libs/ai-agents
+ ```
+
+ This function reads such file and returns a new pathname pointing to the real git dir.
+"
+ (loop with content = (read-file-into-string pathname)
+ with lines = (split #\Newline content)
+ for line in lines
+ for trimmed = (trim line)
+ for splitted = (unless (string= trimmed "")
+ (split #\: trimmed :limit 2))
+ for key = (when (length= 2 splitted)
+ (first splitted))
+ when (and key
+ (string= key "gitdir"))
+ do (return (merge-pathnames (uiop:ensure-directory-pathname
+ (trim (second splitted)))
+ pathname))))
+
+
(defun asdf-system-git-version (system)
(check-type system asdf:system)
(let* ((git-dir
(merge-pathnames (make-pathname :directory '(:relative ".git"))
- (asdf:system-relative-pathname system ""))))
- (if (probe-file git-dir)
- (git-version git-dir)
- nil)))
+ (asdf:system-relative-pathname system "")))
+ (git-dir
+ (when git-dir
+ (probe-file git-dir)))
+ (git-dir
+ (when git-dir
+ (cond
+ ((uiop:directory-pathname-p git-dir)
+ git-dir)
+ (t
+ (read-git-dir-link git-dir))))))
+ (if git-dir
+ (git-version git-dir)
+ nil)))
+
(defun git-version (git-dir)
(let ((head-string (read-first-line
(merge-pathnames (make-pathname :name "HEAD") git-dir))))
(if (starts-with-subseq "ref: " head-string)
- (let ((ref (subseq head-string 5)))
- (values (read-first-line (merge-pathnames ref git-dir)) ref))
- head-string)))
+ (let ((ref (subseq head-string 5)))
+ (values (read-first-line (merge-pathnames ref git-dir)) ref))
+ head-string)))
(defun read-first-line (filename)
(with-open-file (stream filename)
diff --git a/full/utils.lisp b/full/utils.lisp
index ecb9cc3..69126e6 100644
--- a/full/utils.lisp
+++ b/full/utils.lisp
@@ -549,33 +549,72 @@
(reverse translated)))
+(defun normalize-system-name (system-name)
+ (cond
+ ((consp system-name)
+ (cond
+ ((eql (first system-name)
+ :feature)
+ (when (member (second system-name)
+ *features*)
+ (let ((new-dep (third system-name)))
+ (cond
+ ((and (consp new-dep)
+ (eql (first new-dep)
+ :require))
+ ;; Usually :require dependencies
+ ;; load native lisp implementation modules,
+ ;; so we don't need to list them as external dependencies.
+ nil)
+ (t
+ system-name)))))
+ (t
+ (error "Deps ok kind ~S are not supported"
+ (first system-name)))))
+ (t
+ system-name)))
+
+
(defun external-dependencies (system-name)
(let ((primary-name (asdf:primary-system-name system-name))
(processed nil))
(labels ((rec (system-name &optional collected)
- (cond
- ((member system-name processed
- :test #'string-equal)
- collected)
- (t
- (push system-name processed)
- ;; (format t "Processing ~S system~%" system-name)
+ (let ((system-name
+ (normalize-system-name system-name)))
+ (cond
+ ((null system-name)
+ (values))
+ ((member system-name processed
+ :test #'string-equal)
+ collected)
+ (t
+ (push system-name processed)
+ ;; (format t "Processing ~S system~%" system-name)
- (let* ((system (asdf:registered-system system-name))
- (dependencies (asdf/system:system-depends-on system)))
- (loop for dep in dependencies
- for dep-primary = (asdf:primary-system-name dep)
- unless (or (string-equal primary-name dep-primary)
- (member dep collected
- :test #'string-equal))
- collect dep into new-deps
- finally (setf collected
- (append new-deps
- collected)))
- (loop for dep in dependencies
- do (setf collected
- (rec dep collected)))
- collected)))))
+ (let* ((system (asdf:registered-system system-name))
+ (dependencies (asdf/system:system-depends-on system)))
+ (loop for dep in dependencies
+ for normalized-dep = (normalize-system-name dep)
+ for dep-primary = (when normalized-dep
+ (asdf:primary-system-name normalized-dep))
+ unless (or (null dep-primary)
+ (string-equal primary-name dep-primary)
+ (member dep-primary new-deps
+ :test #'string-equal)
+ (member dep-primary collected
+ :test #'string-equal))
+ collect dep-primary into new-deps
+ finally
+ (when new-deps
+ ;; (format t "Appending ~S to ~S~%" new-deps collected)
+ (setf collected
+ (append new-deps
+ collected))))
+
+ (loop for dep in dependencies
+ do (setf collected
+ (rec dep collected)))
+ collected))))))
(sort (rec system-name)
#'string<))))
diff --git a/qlfile.lock b/qlfile.lock
index db1791f..945a8fc 100644
--- a/qlfile.lock
+++ b/qlfile.lock
@@ -1,8 +1,8 @@
("quicklisp" .
(:class qlot/source/dist:source-dist
- :initargs (:distribution "http://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest)
- :version "2024-10-12"))
+ :initargs (:distribution "https://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest)
+ :version "2026-01-01"))
("ultralisp" .
(:class qlot/source/dist:source-dist
- :initargs (:distribution "http://dist.ultralisp.org" :%version :latest)
- :version "20241210203500"))
+ :initargs (:distribution "https://dist.ultralisp.org" :%version :latest)
+ :version "20260410160500"))
diff --git a/src/changelog.lisp b/src/changelog.lisp
index 35c868f..1d74502 100644
--- a/src/changelog.lisp
+++ b/src/changelog.lisp
@@ -155,6 +155,9 @@
"CLEAN-URLS"
;; These objects are not documented yet:
"40ANTS-DOC/COMMONDOC/XREF:XREF"))
+ (0.26.0 2026-04-11
+ "* Supported documentation building for projects cloned as git submodules.
+ * Fixed external dependencies listing in case if some deps use :require.")
(0.25.1 2025-09-13
"* New package 40ants-doc/mito now exports `fixed-dao-table-class` class which should be used instead of Mito's `dao-table-class`.")
(0.25.0 2025-09-13
diff --git a/src/core.lisp b/src/core.lisp
index 682389c..9aa6e04 100644
--- a/src/core.lisp
+++ b/src/core.lisp
@@ -7,8 +7,11 @@
(:import-from #:40ants-doc/object-package)
(:import-from #:40ants-doc/docstring
#:strip-docstring-indentation)
- (:import-from #:serapeum
- #:soft-list-of)
+ ;; We specially do not use soft-list-from-serapeum here
+ ;; to protect core of 40ants-doc from Serapeum and it's transitive
+ ;; dependencies
+ ;; (:import-from #:serapeum
+ ;; #:soft-list-of)
(:export #:defsection
#:exportable-locative-type-p
#:section
@@ -213,6 +216,16 @@
:ignore-packages (list
,@(eval ignore-packages)))))))
+(deftype simple-list-of (type)
+ "A soft constraint for the elements of a list.
+
+Only first element is checked.
+"
+ `(or null
+ (cons ,type
+ t)))
+
+
(defclass section ()
((name
:initarg :name
@@ -252,13 +265,13 @@
:documentation "A list of strings with URLs of other system's documentation.")
(ignore-words
:initarg :ignore-words
- :type (soft-list-of string)
+ :type (simple-list-of string)
:initform nil
:reader section-ignore-words
:documentation "A list of strings to not warn about.")
(ignore-packages
:initarg :ignore-packages
- :type (soft-list-of string)
+ :type (simple-list-of string)
:initform nil
:reader section-ignore-packages
:documentation "A list of strings denoting package names to not warn about."))
diff --git a/test/data/baseline/search/index.html b/test/data/baseline/search/index.html
index 48c577e..441b853 100644
--- a/test/data/baseline/search/index.html
+++ b/test/data/baseline/search/index.html
@@ -4,28 +4,18 @@
content="width=device-width, initial-scale=1">
Search Page
-
-
-
-
-
-
-
-
-
+ href="https://40ants.com/doc/changelog.xml"
+ type="application/rss+xml">
+
+
+
+
+
+
+
-