Showing preview only (1,247K chars total). Download the full file or copy to clipboard to get everything.
Repository: atlas-engineer/nyxt
Branch: master
Commit: aeb6c72cf1ef
Files: 205
Total size: 1.2 MB
Directory structure:
gitextract_qdmfzfut/
├── .dir-locals.el
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── ui_request.md
│ ├── SECURITY.md
│ └── pull_request_template.md
├── .gitignore
├── .gitmodules
├── INSTALL
├── README.org
├── _build/
│ └── README.org
├── assets/
│ ├── Info.plist
│ ├── nyxt.appimage.desktop
│ ├── nyxt.desktop
│ ├── nyxt.icns
│ └── nyxt.metainfo.xml
├── developer-manual.org
├── libraries/
│ ├── analysis/
│ │ ├── README.org
│ │ ├── analysis.lisp
│ │ ├── composite-sequence.lisp
│ │ ├── data.lisp
│ │ ├── dbscan.lisp
│ │ ├── document-vector.lisp
│ │ ├── package.lisp
│ │ ├── section.lisp
│ │ ├── stem.lisp
│ │ ├── tests/
│ │ │ └── tests.lisp
│ │ ├── text-rank.lisp
│ │ └── tokenize.lisp
│ ├── download-manager/
│ │ ├── engine.lisp
│ │ ├── native.lisp
│ │ └── package.lisp
│ ├── nasdf/
│ │ ├── install.lisp
│ │ ├── log.lisp
│ │ ├── nasdf.asd
│ │ ├── nasdf.lisp
│ │ ├── package.lisp
│ │ ├── readme.org
│ │ ├── systems.lisp
│ │ └── tests.lisp
│ ├── password-manager/
│ │ ├── package.lisp
│ │ ├── password-keepassxc.lisp
│ │ ├── password-pass.lisp
│ │ ├── password-security.lisp
│ │ └── password.lisp
│ ├── text-buffer/
│ │ ├── package.lisp
│ │ └── text-buffer.lisp
│ ├── theme/
│ │ ├── README.org
│ │ ├── package.lisp
│ │ ├── tests/
│ │ │ └── tests.lisp
│ │ ├── theme.lisp
│ │ └── utilities.lisp
│ └── user-interface/
│ ├── package.lisp
│ └── user-interface.lisp
├── licenses/
│ ├── ASSET-LICENSE
│ ├── DejaVu Fonts License.txt
│ └── SOURCE-LICENSE
├── makefile
├── nyxt.asd
├── source/
│ ├── about.lisp
│ ├── browser.lisp
│ ├── buffer.lisp
│ ├── clipboard.lisp
│ ├── color.lisp
│ ├── command-commands.lisp
│ ├── command.lisp
│ ├── concurrency.lisp
│ ├── conditions.lisp
│ ├── configuration-commands.lisp
│ ├── configuration.lisp
│ ├── describe.lisp
│ ├── dom.lisp
│ ├── external-editor.lisp
│ ├── foreign-interface.lisp
│ ├── global.lisp
│ ├── help.lisp
│ ├── history.lisp
│ ├── input.lisp
│ ├── inspector.lisp
│ ├── keyscheme.lisp
│ ├── manual.lisp
│ ├── message.lisp
│ ├── mode/
│ │ ├── annotate.lisp
│ │ ├── autofill.lisp
│ │ ├── base.lisp
│ │ ├── blocker.lisp
│ │ ├── bookmark.lisp
│ │ ├── bookmarklets.lisp
│ │ ├── buffer-listing.lisp
│ │ ├── certificate-exception.lisp
│ │ ├── cruise-control.lisp
│ │ ├── document.lisp
│ │ ├── download.lisp
│ │ ├── emacs.lisp
│ │ ├── expedition.lisp
│ │ ├── file-manager.lisp
│ │ ├── force-https.lisp
│ │ ├── help.lisp
│ │ ├── hint-prompt-buffer.lisp
│ │ ├── hint.lisp
│ │ ├── history-migration.lisp
│ │ ├── history.lisp
│ │ ├── input-edit.lisp
│ │ ├── keyscheme.lisp
│ │ ├── macro-edit.lisp
│ │ ├── message.lisp
│ │ ├── no-image.lisp
│ │ ├── no-script.lisp
│ │ ├── no-sound.lisp
│ │ ├── no-webgl.lisp
│ │ ├── passthrough.lisp
│ │ ├── password.lisp
│ │ ├── process.lisp
│ │ ├── prompt-buffer.lisp
│ │ ├── proxy.lisp
│ │ ├── reading-line.lisp
│ │ ├── repeat.lisp
│ │ ├── search-buffer.lisp
│ │ ├── small-web.lisp
│ │ ├── spell-check.lisp
│ │ ├── style.lisp
│ │ ├── user-script.lisp
│ │ ├── vi.lisp
│ │ ├── visual.lisp
│ │ └── watch.lisp
│ ├── mode.lisp
│ ├── package.lisp
│ ├── parenscript-macro.lisp
│ ├── prompt-buffer.lisp
│ ├── recent-buffers.lisp
│ ├── renderer/
│ │ ├── electron.lisp
│ │ ├── gi-gtk.lisp
│ │ └── gtk.lisp
│ ├── renderer-script.lisp
│ ├── renderer.lisp
│ ├── search-engine.lisp
│ ├── spinneret-tags.lisp
│ ├── start.lisp
│ ├── status.lisp
│ ├── time.lisp
│ ├── tutorial.lisp
│ ├── types.lisp
│ ├── urls.lisp
│ ├── user-classes.lisp
│ ├── user-files.lisp
│ ├── user-interface.lisp
│ ├── utilities.lisp
│ └── window.lisp
└── tests/
├── benchmarks/
│ ├── package.lisp
│ └── prompter.lisp
├── define-configuration.lisp
├── mode/
│ ├── annotate.lisp
│ ├── autofill.lisp
│ ├── base.lisp
│ ├── blocker.lisp
│ ├── bookmark.lisp
│ ├── bookmarklets.lisp
│ ├── buffer-listing.lisp
│ ├── certificate-exception.lisp
│ ├── cruise-control.lisp
│ ├── document.lisp
│ ├── download.lisp
│ ├── emacs.lisp
│ ├── expedition.lisp
│ ├── file-manager.lisp
│ ├── force-https.lisp
│ ├── help.lisp
│ ├── hint-prompt-buffer.lisp
│ ├── hint.lisp
│ ├── history.lisp
│ ├── input-edit.lisp
│ ├── keyscheme.lisp
│ ├── macro-edit.lisp
│ ├── message.lisp
│ ├── no-image.lisp
│ ├── no-script.lisp
│ ├── no-sound.lisp
│ ├── no-webgl.lisp
│ ├── passthrough.lisp
│ ├── password.lisp
│ ├── process.lisp
│ ├── prompt-buffer.lisp
│ ├── proxy.lisp
│ ├── reading-line.lisp
│ ├── reduce-tracking.lisp
│ ├── repeat.lisp
│ ├── search-buffer.lisp
│ ├── small-web.lisp
│ ├── spell-check.lisp
│ ├── style.lisp
│ ├── user-script.lisp
│ ├── vi.lisp
│ ├── visual.lisp
│ └── watch.lisp
├── mode.lisp
├── package.lisp
├── prompt-buffer.lisp
├── renderer/
│ ├── custom-schemes.lisp
│ ├── package.lisp
│ ├── search-buffer.lisp
│ └── set-url.lisp
├── test-data/
│ ├── hint-mode-html-document.html
│ └── history.lisp
├── urls.lisp
└── user-script-parsing.lisp
================================================
FILE CONTENTS
================================================
================================================
FILE: .dir-locals.el
================================================
((nil . ((fill-column . 80)
(project-vc-ignores . ("./_build"))
(require-final-newline . t)
(eval . (add-hook 'before-save-hook 'delete-trailing-whitespace nil t))))
(org-mode . ((org-edit-src-content-indentation 0)))
(lisp-mode
. ((eval . (cl-flet ((enhance-imenu-lisp
(&rest keywords)
(dolist (keyword keywords)
(let ((prefix (when (listp keyword) (cl-second keyword)))
(keyword (if (listp keyword)
(cl-first keyword)
keyword)))
(add-to-list
'lisp-imenu-generic-expression
(list (purecopy (concat (capitalize keyword)
(if (string= (substring-no-properties keyword -1) "s")
"es"
"s")))
(purecopy (concat "^\\s-*("
(regexp-opt
(list (if prefix
(concat prefix "-" keyword)
keyword)
(concat prefix "-" keyword))
t)
"\\s-+\\(" lisp-mode-symbol-regexp "\\)"))
2))))))
;; This adds the argument to the list of imenu known keywords.
(enhance-imenu-lisp
'("bookmarklet-command" "define")
'("class" "define")
'("command" "define")
'("ffi-method" "define")
'("ffi-generic" "define")
'("function" "define")
'("internal-page-command" "define")
'("internal-page-command-global" "define")
'("mode" "define")
'("parenscript" "define")
"defpsmacro"))))))
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Bug report
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
**Steps to reproduce the issue**
**Information**
- OS Name+Version:
- Installation method (Flatpak, Guix, package manager, build from source):
- Output of Nyxt command `show-system-information`:
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
**Describe the solution you'd like**
**Describe alternatives you've considered**
**Additional context**
================================================
FILE: .github/ISSUE_TEMPLATE/ui_request.md
================================================
---
name: UI request
about: Suggest a UI change for this project
title: ''
labels: ui/ux
assignees: ''
---
**Please describe the UI issue:**
**Describe the solution you'd like**
**Describe alternatives you've considered**
**Additional context:**
**Screenshots/Mock ups:**
================================================
FILE: .github/SECURITY.md
================================================
# Security Policy
## Supported Versions
Only the latest stable version is currently supported with security updates.
## Reporting a Vulnerability
hello@atlas.engineer
================================================
FILE: .github/pull_request_template.md
================================================
# Description
- Please include a summary of the change.
Fixes # (issue)
# Checklist:
- [ ] Git branch state is mergable.
- [ ] Changelog is up to date (via a separate commit).
- [ ] New dependencies are accounted for.
- [ ] Documentation is up to date.
- [ ] Compilation and tests (`(asdf:test-system :nyxt/<renderer>)`)
- No new compilation warnings.
- Tests are sufficient.
================================================
FILE: .gitignore
================================================
# Ignore build artifacts
nyxt
build/
node_modules/
package.json
package-lock.json
# Ignore compiled lisp files
*.FASL
*.fasl
*.fas
*.lisp-temp
*.dfsl
*.pfsl
*.d64fsl
*.p64fsl
*.lx64fsl
*.lx32fsl
*.dx64fsl
*.dx32fsl
*.fx64fsl
*.fx32fsl
*.sx64fsl
*.sx32fsl
*.wx64fsl
*.wx32fsl
# Ignore PNG and XCF
*.xcf
*.png
# Generated documentation
manual.html
# Ignore etags/ctags
TAGS
# Ignore C object files and libraries
*.o
*.so
================================================
FILE: .gitmodules
================================================
[submodule "_build/alexandria"]
path = _build/alexandria
url = https://gitlab.common-lisp.net/alexandria/alexandria.git
shallow = true
[submodule "_build/bordeaux-threads"]
path = _build/bordeaux-threads
url = https://github.com/sionescu/bordeaux-threads
shallow = true
[submodule "_build/calispel"]
path = _build/calispel
url = https://github.com/hawkir/calispel
shallow = true
[submodule "_build/cl-jpl-util"]
path = _build/cl-jpl-util
url = https://github.com/hawkir/cl-jpl-util
shallow = true
[submodule "_build/trivial-garbage"]
path = _build/trivial-garbage
url = https://github.com/trivial-garbage/trivial-garbage
shallow = true
[submodule "_build/cl-containers"]
path = _build/cl-containers
url = https://github.com/gwkkwg/cl-containers
shallow = true
[submodule "_build/metatilities-base"]
path = _build/metatilities-base
url = https://github.com/gwkkwg/metatilities-base
shallow = true
[submodule "_build/cl-custom-hash-table"]
path = _build/cl-custom-hash-table
url = https://github.com/metawilm/cl-custom-hash-table
shallow = true
[submodule "_build/cl-ppcre"]
path = _build/cl-ppcre
url = https://github.com/edicl/cl-ppcre
shallow = true
[submodule "_build/flexi-streams"]
path = _build/flexi-streams
url = https://github.com/edicl/flexi-streams
shallow = true
[submodule "_build/trivial-gray-streams"]
path = _build/trivial-gray-streams
url = https://github.com/trivial-gray-streams/trivial-gray-streams
shallow = true
[submodule "_build/cl-prevalence"]
path = _build/cl-prevalence
url = https://github.com/40ants/cl-prevalence
shallow = true
[submodule "_build/s-sysdeps"]
path = _build/s-sysdeps
url = https://github.com/svenvc/s-sysdeps
shallow = true
[submodule "_build/usocket"]
path = _build/usocket
url = https://github.com/usocket/usocket/
shallow = true
[submodule "_build/split-sequence"]
path = _build/split-sequence
url = https://github.com/sharplispers/split-sequence
shallow = true
[submodule "_build/closer-mop"]
path = _build/closer-mop
url = https://github.com/pcostanza/closer-mop
shallow = true
[submodule "_build/cluffer"]
path = _build/cluffer
url = https://github.com/robert-strandh/cluffer
shallow = true
[submodule "_build/Acclimation"]
path = _build/Acclimation
url = https://github.com/robert-strandh/Acclimation
shallow = true
[submodule "_build/Clump"]
path = _build/Clump
url = https://github.com/robert-strandh/Clump
shallow = true
[submodule "_build/dexador"]
path = _build/dexador
url = https://github.com/fukamachi/dexador
shallow = true
[submodule "_build/babel"]
path = _build/babel
url = https://github.com/cl-babel/babel
shallow = true
[submodule "_build/trivial-features"]
path = _build/trivial-features
url = https://github.com/trivial-features/trivial-features
shallow = true
[submodule "_build/fast-http"]
path = _build/fast-http
url = https://github.com/fukamachi/fast-http
shallow = true
[submodule "_build/proc-parse"]
path = _build/proc-parse
url = https://github.com/fukamachi/proc-parse
shallow = true
[submodule "_build/anaphora"]
path = _build/anaphora
url = https://github.com/tokenrove/anaphora
shallow = true
[submodule "_build/xsubseq"]
path = _build/xsubseq
url = https://github.com/fukamachi/xsubseq
shallow = true
[submodule "_build/smart-buffer"]
path = _build/smart-buffer
url = https://github.com/fukamachi/smart-buffer
shallow = true
[submodule "_build/cl-unicode"]
path = _build/cl-unicode
url = https://github.com/edicl/cl-unicode
shallow = true
[submodule "_build/named-readtables"]
path = _build/named-readtables
url = https://github.com/melisgl/named-readtables
shallow = true
[submodule "_build/trivial-types"]
path = _build/trivial-types
url = https://github.com/m2ym/trivial-types
shallow = true
[submodule "_build/quri"]
path = _build/quri
url = https://github.com/fukamachi/quri
shallow = true
[submodule "_build/fast-io"]
path = _build/fast-io
url = https://github.com/rpav/fast-io
shallow = true
[submodule "_build/static-vectors"]
path = _build/static-vectors
url = https://github.com/sionescu/static-vectors
shallow = true
[submodule "_build/cffi"]
path = _build/cffi
url = https://github.com/cffi/cffi
shallow = true
[submodule "_build/chunga"]
path = _build/chunga
url = https://github.com/edicl/chunga
shallow = true
[submodule "_build/cl-cookie"]
path = _build/cl-cookie
url = https://github.com/fukamachi/cl-cookie
shallow = true
[submodule "_build/local-time"]
path = _build/local-time
url = https://github.com/dlowe-net/local-time
shallow = true
[submodule "_build/trivial-mimes"]
path = _build/trivial-mimes
url = https://github.com/Shinmera/trivial-mimes
shallow = true
[submodule "_build/cl-base64"]
path = _build/cl-base64
url = https://gitlab.common-lisp.net/nyxt/cl-base64.git
shallow = true
[submodule "_build/cl-plus-ssl"]
path = _build/cl-plus-ssl
url = https://github.com/cl-plus-ssl/cl-plus-ssl
shallow = true
[submodule "_build/drakma"]
path = _build/drakma
url = https://github.com/edicl/drakma
shallow = true
[submodule "_build/puri"]
path = _build/puri
url = https://gitlab.common-lisp.net/nyxt/puri.git
shallow = true
[submodule "_build/cl-enchant"]
path = _build/cl-enchant
url = https://github.com/tlikonen/cl-enchant
shallow = true
[submodule "_build/fset"]
path = _build/fset
url = https://github.com/slburson/fset
shallow = true
[submodule "_build/misc-extensions"]
path = _build/misc-extensions
url = https://gitlab.common-lisp.net/misc-extensions/misc-extensions.git
shallow = true
[submodule "_build/iolib"]
path = _build/iolib
url = https://github.com/sionescu/iolib
shallow = true
[submodule "_build/idna"]
path = _build/idna
url = https://github.com/antifuchs/idna
shallow = true
[submodule "_build/swap-bytes"]
path = _build/swap-bytes
url = https://github.com/sionescu/swap-bytes
shallow = true
[submodule "_build/log4cl"]
path = _build/log4cl
url = https://github.com/sharplispers/log4cl
shallow = true
[submodule "_build/moptilities"]
path = _build/moptilities
url = https://github.com/gwkkwg/moptilities/
shallow = true
[submodule "_build/parenscript"]
path = _build/parenscript
url = https://gitlab.common-lisp.net/parenscript/parenscript
shallow = true
[submodule "_build/plump"]
path = _build/plump
url = https://github.com/Shinmera/plump
shallow = true
[submodule "_build/array-utils"]
path = _build/array-utils
url = https://github.com/Shinmera/array-utils
shallow = true
[submodule "_build/documentation-utils"]
path = _build/documentation-utils
url = https://github.com/Shinmera/documentation-utils
shallow = true
[submodule "_build/trivial-indent"]
path = _build/trivial-indent
url = https://github.com/Shinmera/trivial-indent
shallow = true
[submodule "_build/serapeum"]
path = _build/serapeum
url = https://github.com/ruricolist/serapeum
shallow = true
[submodule "_build/nhooks"]
path = _build/nhooks
url = https://github.com/atlas-engineer/nhooks
shallow = true
[submodule "_build/trivia"]
path = _build/trivia
url = https://github.com/guicho271828/trivia
shallow = true
[submodule "_build/optima"]
path = _build/optima
url = https://github.com/m2ym/optima
shallow = true
[submodule "_build/lisp-namespace"]
path = _build/lisp-namespace
url = https://github.com/guicho271828/lisp-namespace
shallow = true
[submodule "_build/trivial-cltl2"]
path = _build/trivial-cltl2
url = https://github.com/Zulu-Inuoe/trivial-cltl2
shallow = true
[submodule "_build/type-i"]
path = _build/type-i
url = https://github.com/guicho271828/type-i
shallow = true
[submodule "_build/introspect-environment"]
path = _build/introspect-environment
url = https://github.com/Bike/introspect-environment
shallow = true
[submodule "_build/string-case"]
path = _build/string-case
url = https://github.com/pkhuong/string-case
shallow = true
[submodule "_build/parse-number"]
path = _build/parse-number
url = https://github.com/sharplispers/parse-number/
shallow = true
[submodule "_build/parse-declarations"]
path = _build/parse-declarations
url = https://gitlab.common-lisp.net/parse-declarations/parse-declarations.git
shallow = true
[submodule "_build/global-vars"]
path = _build/global-vars
url = https://github.com/lmj/global-vars
shallow = true
[submodule "_build/trivial-file-size"]
path = _build/trivial-file-size
url = https://github.com/ruricolist/trivial-file-size
shallow = true
[submodule "_build/trivial-macroexpand-all"]
path = _build/trivial-macroexpand-all
url = https://github.com/cbaggers/trivial-macroexpand-all
shallow = true
[submodule "_build/cl-str"]
path = _build/cl-str
url = https://github.com/vindarel/cl-str
shallow = true
[submodule "_build/cl-change-case"]
path = _build/cl-change-case
url = https://github.com/rudolfochrist/cl-change-case
shallow = true
[submodule "_build/trivial-clipboard"]
path = _build/trivial-clipboard
url = https://github.com/snmsts/trivial-clipboard
shallow = true
[submodule "_build/trivial-package-local-nicknames"]
path = _build/trivial-package-local-nicknames
url = https://github.com/phoe/trivial-package-local-nicknames
shallow = true
[submodule "_build/unix-opts"]
path = _build/unix-opts
url = https://github.com/atlas-engineer/unix-opts
shallow = true
[submodule "_build/cl-webkit"]
path = _build/cl-webkit
url = https://github.com/joachifm/cl-webkit
shallow = true
[submodule "_build/cl-gobject-introspection"]
path = _build/cl-gobject-introspection
url = https://github.com/andy128k/cl-gobject-introspection
shallow = true
[submodule "_build/lparallel"]
path = _build/lparallel
url = https://github.com/lmj/lparallel/
shallow = true
[submodule "_build/jpl-queues"]
path = _build/jpl-queues
url = https://gitlab.common-lisp.net/nyxt/jpl-queues.git
shallow = true
[submodule "_build/mt19937"]
path = _build/mt19937
url = https://gitlab.common-lisp.net/nyxt/mt19937
shallow = true
[submodule "_build/s-xml"]
path = _build/s-xml
url = https://gitlab.common-lisp.net/s-xml/s-xml
shallow = true
[submodule "_build/cl-utilities"]
path = _build/cl-utilities
url = https://gitlab.common-lisp.net/cl-utilities/cl-utilities
shallow = true
[submodule "_build/cl-qrencode"]
path = _build/cl-qrencode
url = https://github.com/jnjcc/cl-qrencode
shallow = true
[submodule "_build/clss"]
path = _build/clss
url = https://github.com/Shinmera/clss
shallow = true
[submodule "_build/spinneret"]
path = _build/spinneret
url = https://github.com/ruricolist/spinneret/
shallow = true
[submodule "_build/salza2"]
path = _build/salza2
url = https://github.com/xach/salza2
shallow = true
[submodule "_build/zpng"]
path = _build/zpng
url = https://github.com/xach/zpng
shallow = true
[submodule "_build/iterate"]
path = _build/iterate
url = https://gitlab.common-lisp.net/iterate/iterate.git
shallow = true
[submodule "_build/cl-gopher"]
path = _build/cl-gopher
url = https://github.com/knusbaum/cl-gopher
shallow = true
[submodule "_build/phos"]
path = _build/phos
url = https://github.com/omar-polo/phos
shallow = true
[submodule "_build/cl-tld"]
path = _build/cl-tld
url = https://github.com/lu4nx/cl-tld
shallow = true
[submodule "_build/nfiles"]
path = _build/nfiles
url = https://github.com/atlas-engineer/nfiles
shallow = true
[submodule "_build/nkeymaps"]
path = _build/nkeymaps
url = https://github.com/atlas-engineer/nkeymaps
shallow = true
[submodule "_build/py-configparser"]
path = _build/py-configparser
url = https://gitlab.common-lisp.net/nyxt/py-configparser
shallow = true
[submodule "_build/trivial-custom-debugger"]
path = _build/trivial-custom-debugger
url = https://github.com/phoe/trivial-custom-debugger
shallow = true
[submodule "_build/lisp-unit2"]
path = _build/lisp-unit2
url = https://github.com/AccelerationNet/lisp-unit2
shallow = true
[submodule "_build/nsymbols"]
path = _build/nsymbols
url = https://github.com/atlas-engineer/nsymbols
shallow = true
[submodule "_build/LASS"]
path = _build/LASS
url = https://github.com/Shinmera/LASS
shallow = true
[submodule "_build/njson"]
path = _build/njson
url = https://github.com/atlas-engineer/njson
shallow = true
[submodule "_build/nclasses"]
path = _build/nclasses
url = https://github.com/atlas-engineer/nclasses/
shallow = true
[submodule "_build/prompter"]
path = _build/prompter
url = https://github.com/atlas-engineer/prompter
shallow = true
[submodule "_build/chipz"]
path = _build/chipz
url = https://github.com/sharplispers/chipz
shallow = true
[submodule "_build/cl-cffi-gtk"]
path = _build/cl-cffi-gtk
url = https://github.com/sharplispers/cl-cffi-gtk
shallow = true
[submodule "_build/cl-json"]
path = _build/cl-json
url = https://github.com/sharplispers/cl-json
shallow = true
[submodule "_build/cl-sqlite"]
path = _build/cl-sqlite
url = https://github.com/TeMPOraL/cl-sqlite
shallow = true
[submodule "_build/cl-electron"]
path = _build/cl-electron
url = https://github.com/atlas-engineer/cl-electron/
shallow = true
[submodule "_build/cl-colors-ng"]
path = _build/cl-colors-ng
url = https://codeberg.org/cage/cl-colors-ng.git
shallow = true
[submodule "_build/cl-interpol"]
path = _build/cl-interpol
url = https://github.com/edicl/cl-interpol
shallow = true
[submodule "_build/symbol-munger"]
path = _build/symbol-munger
url = https://github.com/AccelerationNet/symbol-munger
shallow = true
[submodule "_build/in-nomine"]
path = _build/in-nomine
url = https://github.com/phoe/in-nomine
shallow = true
[submodule "_build/trivial-arguments"]
path = _build/trivial-arguments
url = https://github.com/Shinmera/trivial-arguments.git
shallow = true
================================================
FILE: INSTALL
================================================
Usage:
make all # Generate Nyxt binary at $PWD.
make install # Install Nyxt.
make doc # Generate Nyxt static documentation.
DESTDIR and PREFIX set the target destination. Both must be absolute paths.
When unbound, DESTDIR is set to / and PREFIX is set to $DESTDIR/usr/local/.
NASDF_SOURCE_PATH sets where the source files will be installed. When unbound,
it is set to $PREFIX/share/.
When NYXT_SUBMODULES is "true" (the default), all Lisp dependencies are searched
at ./_build. Otherwise, they need to be made visible to ASDF by other means.
In case you have received an archive that includes the source of these Lisp
dependencies, then it all should work out of the box.
NYXT_RENDERER sets the renderer, by default "electron".
NYXT_VERSION forces the version number, in the rare eventuality that it can't be
fetched in another way.
The static documentation is particularly useful when it can't be consulted from
Nyxt itself (where it is dynamically generated at runtime).
================================================
FILE: README.org
================================================
* Nyxt browser
#+html: <img src="https://nyxt-browser.com/static/image/nyxt_256x256.png" align="right"/>
*Nyxt* [nýkst] is a keyboard-driven web browser designed for hackers. Inspired by
Emacs and Vim, it has familiar keybindings ([[https://en.wikipedia.org/wiki/Emacs][Emacs]], [[https://en.wikipedia.org/wiki/Vim_(text_editor)][vi]], [[https://en.wikipedia.org/wiki/IBM_Common_User_Access][CUA]]), and is infinitely
extensible in Lisp.
*Attention:* Nyxt is under active development. Please feel free to [[https://github.com/atlas-engineer/nyxt/issues][report]] bugs,
instabilities or feature wishes.
-----
* Features
For an exhaustive description of all of the features, please refer to the
manual.
** Fast tab switching
Switch easily between your open tabs via fuzzy search. If you are looking for
~https://www.example.com~, you could type in ~ele~, ~exa~, ~epl~, or any other
matching series of letters.
#+html: <img src="https://nyxt-browser.com/static/image/switch-buffer.png" align="center"/>
** Multiple marking
Commands can accept multiple inputs, allowing you to quickly perform an
operation against multiple objects. In the example below we simultaneously open
several bookmarks.
#+html: <img src="https://nyxt-browser.com/static/image/multi-select.png" align="center"/>
** Powerful bookmarks
Bookmark a page with tags. Search bookmarks with compound queries. Capture
more data about your bookmarks, and group and wrangle them in any way you like.
#+html: <img src="https://nyxt-browser.com/static/image/bookmark.png" align="center"/>
** Multi tab search
Search multiple tabs at the same time, and view all the results in a single
window. Jump quickly through your open tabs to find what you need.
#+html: <img src="https://nyxt-browser.com/static/image/multi-search.png" align="center"/>
** History as a tree
History is represented as a tree that you can traverse. Smarter than the
"forwards-backwards" abstraction found in other browsers, the tree makes sure
you never lose track of where you've been.
#+html: <img src="https://nyxt-browser.com/static/image/history.png" align="center"/>
* Installation
Supported platforms:
- GNU/Linux
- macOS (in development)
- Windows (in development)
- FreeBSD (unofficial)
** GNU/Linux
The Nyxt team maintains the following distribution means:
- [[https://flathub.org/apps/engineer.atlas.Nyxt][Nyxt on Flathub]]
[[https://repology.org/project/nyxt/versions][Non-official distribution means are supported by the community as well]]. We're
not accountable for their quality, so we kindly ask to report issues to the
maintainers of those packaging efforts.
* Contributing
Please refer to the [[file:developer-manual.org][developer's documentation]].
================================================
FILE: _build/README.org
================================================
This directory contains all Common Lisp dependencies. They are fetched via Git
submodules. This gives us good reproducibility and control, unlike with
Quicklisp which might not have the right versions.
We store these in a directory that's excluded from recursion by ASDF.
as per =asdf/source-registry:*source-registry-exclusions*=.
This way we won't conflict with user or system libraries.
================================================
FILE: assets/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>Nyxt</string>
<key>CFBundleIconFile</key>
<string>nyxt.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleGetInfoString</key>
<string>Nyxt</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>nyxt</string>
<key>CFBundleIdentifier</key>
<string>engineer.Atlas.Nyxt</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>http URL</string>
<key>CFBundleURLSchemes</key>
<array>
<string>http</string>
</array>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>https URL</string>
<key>CFBundleURLSchemes</key>
<array>
<string>https</string>
</array>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>gopher URL</string>
<key>CFBundleURLSchemes</key>
<array>
<string>gopher</string>
</array>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>gemini URL</string>
<key>CFBundleURLSchemes</key>
<array>
<string>gemini</string>
</array>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>html</string>
<string>xhtml</string>
</array>
</dict>
</array>
</dict>
</plist>
================================================
FILE: assets/nyxt.appimage.desktop
================================================
[Desktop Entry]
Name=Nyxt
Comment=Web Browser for Hackers
GenericName=Web Browser
Keywords=Internet;WWW;Browser;Web;Explorer
Exec="~a"
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=nyxt
Categories=Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
StartupNotify=true
StartupWMClass=nyxt
================================================
FILE: assets/nyxt.desktop
================================================
[Desktop Entry]
Name=Nyxt
Comment=Web Browser for Hackers
GenericName=Web Browser
Keywords=Internet;WWW;Browser;Web;Explorer
Exec=nyxt %u
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=nyxt
Categories=Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
StartupNotify=true
StartupWMClass=nyxt
================================================
FILE: assets/nyxt.metainfo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop-application">
<!-- Spec: https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html -->
<id>engineer.atlas.Nyxt</id>
<metadata_license>CC-BY-SA-3.0</metadata_license>
<project_license>BSD-3-Clause</project_license>
<content_rating type="oars-1.1" />
<developer_name>Atlas Engineer</developer_name>
<name>Nyxt</name>
<summary>The hacker's browser</summary>
<description>
<p>Quickly analyze, navigate, and extract information from the
Internet. Nyxt is fully hackable — all of its source code can be
introspected, modified, and tweaked to your exact specification.</p>
</description>
<categories>
<category>Network</category>
</categories>
<url type="homepage">https://nyxt-browser.com/</url>
<url type="vcs-browser">https://github.com/atlas-engineer/nyxt</url>
<url type="bugtracker">https://github.com/atlas-engineer/nyxt/issues</url>
<url type="faq">https://nyxt-browser.com/faq</url>
<url type="help">https://discourse.atlas.engineer/</url>
<url type="donation">https://nyxt-browser.com/purchase</url>
<!-- <url type="translate"></url> -->
<url type="contact">https://atlas.engineer/contact</url>
<url type="contribute">https://github.com/atlas-engineer/nyxt/blob/master/developer-manual.org</url>
<launchable type="desktop-id">nyxt.desktop</launchable>
<provides>
<binary>nyxt</binary>
</provides>
<screenshots>
<screenshot type="default">
<image type="source" width="1108" height="881">https://nyxt-browser.com/static/image/switch-buffer.png</image>
<caption>Fast tab switching</caption>
</screenshot>
<screenshot>
<image type="source" width="1108" height="881">https://nyxt-browser.com/static/image/multi-select.png</image>
<caption>Multiple marking</caption>
</screenshot>
<screenshot>
<image type="source" width="1108" height="881">https://nyxt-browser.com/static/image/bookmark.png</image>
<caption>Powerful bookmarks</caption>
</screenshot>
<screenshot>
<image type="source" width="1108" height="881">https://nyxt-browser.com/static/image/multi-search.png</image>
<caption>Multi tab search</caption>
</screenshot>
<screenshot>
<image type="source" width="1108" height="881">https://nyxt-browser.com/static/image/history.png</image>
<caption>History as a tree</caption>
</screenshot>
</screenshots>
<!-- Only those published on Flathub are listed -->
<releases>
<release version="3.11.8" date="2024-08-01">
<url>https://nyxt-browser.com/article/release-3.11.8.org</url>
</release>
<release version="3.11.7" date="2024-05-31">
<url>https://nyxt-browser.com/article/release-3.11.7.org</url>
</release>
<release version="3.11.6" date="2024-04-08">
<url>https://nyxt-browser.com/article/release-3.11.6.org</url>
</release>
<release version="3.11.5" date="2024-03-18">
<url>https://nyxt-browser.com/article/release-3.11.5.org</url>
</release>
<release version="3.11.4" date="2024-03-04">
<url>https://nyxt-browser.com/article/release-3.11.4.org</url>
</release>
<release version="3.11.3" date="2024-02-20">
<url>https://nyxt-browser.com/article/release-3.11.3.org</url>
</release>
<release version="3.11.2" date="2024-02-05">
<url>https://nyxt-browser.com/article/release-3.11.2.org</url>
</release>
<release version="3.11.1" date="2024-01-22">
<url>https://nyxt-browser.com/article/release-3.11.1.org</url>
</release>
<release version="3.11.0" date="2024-01-08">
<url>https://nyxt-browser.com/article/release-3.11.0.org</url>
</release>
<release version="3.10.0" date="2023-12-11">
<url>https://nyxt-browser.com/article/release-3.10.0.org</url>
</release>
<release version="3.9.2" date="2023-11-13">
<url>https://nyxt-browser.com/article/release-3.9.2.org</url>
</release>
<release version="3.9.1" date="2023-10-23">
<url>https://nyxt-browser.com/article/release-3.9.1.org</url>
</release>
<release version="3.9.0" date="2023-10-09">
<url>https://nyxt-browser.com/article/release-3.9.0.org</url>
</release>
<release version="3.8.0" date="2023-09-25">
<url>https://nyxt-browser.com/article/release-3.8.0.org</url>
</release>
<release version="3.7.0" date="2023-09-11">
<url>https://nyxt-browser.com/article/release-3.7.0.org</url>
</release>
<release version="3.6.1" date="2023-08-28">
<url>https://nyxt-browser.com/article/release-3.6.1.org</url>
</release>
</releases>
</component>
================================================
FILE: developer-manual.org
================================================
#+TITLE: Nyxt Developer's Manual
# Install org-make-toc so the TOC below will be automatically generated.
# https://github.com/alphapapa/org-make-toc
* Table of contents :TOC:
:PROPERTIES:
:TOC: :include all :ignore this
:END:
:CONTENTS:
- [[#bill-of-materials][Bill of Materials]]
- [[#source][Source]]
- [[#common-lisp][Common Lisp]]
- [[#web-renderers][Web renderers]]
- [[#webkitgtk][WebKitGTK]]
- [[#electron][Electron]]
- [[#other][Other]]
- [[#development-environment][Development environment]]
- [[#tests][Tests]]
- [[#installation][Installation]]
- [[#contributing][Contributing]]
- [[#help][Help]]
- [[#commit-style][Commit style]]
- [[#branch-management][Branch management]]
- [[#programming-conventions][Programming conventions]]
:END:
* Bill of Materials
** Source
Either get a tarball (=nyxt-<version>-source-with-submodules.tar.xz=) from a
[[https://github.com/atlas-engineer/nyxt/releases][tagged release]], or clone as a git repository:
#+begin_src sh
mkdir -p ~/common-lisp
git clone --recurse-submodules https://github.com/atlas-engineer/nyxt ~/common-lisp/nyxt
#+end_src
** Common Lisp
Nyxt is written in Common Lisp. Currently, we only target one of its
implementations - [[http://www.sbcl.org/][SBCL]].
Nyxt also depends on Common Lisp libraries. These are bundled in the tarball
mentioned above or fetched as Git submodules (under =./_build=).
Note for advanced users: the single source of truth for CL libraries is dictated
by the Git submodules. Any Nyxt build that deviates from it is considered
unofficial. See environment variable =NYXT_SUBMODULES= defined in the makefile
to override the default behavior.
** Web renderers
Nyxt is designed to be web engine agnostic so its dependencies vary.
*** WebKitGTK
Using the latest [[https://webkitgtk.org][WebKitGTK]] version is advised for security concerns. The oldest
version that supports all features is 2.36.
The packages that provide the following shared objects are required:
- libwebkit2gtk-4.1.so
- libgobject-2.0.so
- libgirepository-1.0.so
- libglib-2.0.so
- libgthread-2.0.so
- libgio-2.0.so
- libcairo.so
- libpango-1.0.so
- libpangocairo-1.0.so
- libgdk_pixbuf-2.0.so
- libgdk-3.so
- libgtk-3.so
To improve media stream it is recommended to install =gst-libav= and the
following plugins:
- gst-plugins-bad
- gst-plugins-base
- gst-plugins-good
- gst-plugins-ugly
*** Electron
Experimental support for [[https://www.electronjs.org/][Electron]]. Further documentation soon.
** Other
The packages that provide the following shared objects are required:
- libssl.so.3
- libcrypto.so.3
- libfixposix.so.3
- libsqlite3.so
Additionally, the following packages:
- xclip :: when using X system;
- wl-clipboard :: when using Wayland;
- enchant :: spellchecking (optional).
* Development environment
Lisp favors incremental program development meaning that you make some changes
and compile them. In other words, there's no need to compile the whole codebase
or even restart the program.
The typical Common Lisp IDE is [[https://github.com/slime/slime][SLIME]] (or its fork [[https://github.com/joaotavora/sly][SLY]]), which requires being
comfortable with Emacs. Add the snippet below to Emacs' init file.
#+begin_src emacs-lisp
(setq slime-lisp-implementations
'((nyxt ("sbcl" "--dynamic-space-size 3072")
:env ("CL_SOURCE_REGISTRY=~/common-lisp//:~/common-lisp/nyxt/_build//"))))
#+end_src
Start the REPL by issuing =M-- M-x sly RET nyxt RET= and evaluate:
#+begin_src lisp
(asdf:load-system :nyxt/gi-gtk)
(nyxt:start)
#+end_src
Note that:
- [[https://asdf.common-lisp.dev/asdf/Configuring-ASDF-to-find-your-systems.html][ASDF must be configured to find the required systems]];
- =cffi= must be configured to find the required shared objects by setting env
var =LD_LIBRARY_PATH= or =cffi:*foreign-library-directories*=.
** Tests
It is recommended to restart the Lisp image before and after running the tests
since some of them are stateful:
#+begin_src lisp
(asdf:test-system :nyxt/gi-gtk)
#+end_src
* Installation
Nyxt uses the =Make= build system. Run =make= to display the documentation or
see the [[../makefile][Makefile]] for more details.
* Contributing
Nyxt is a joint effort and we welcome contributors! You can find tasks [[https://github.com/atlas-engineer/nyxt/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue][on our
issue tracker]] to suit your interests and skills. Please fork the project and
open a pull request (PR) on GitHub to undergo the reviewing process. Refer to
the [[*Branch management][branch management section]] for more detailed information.
Please resist the temptation of discussing changes without drafting its
implementation. Currently, we value pragmatism over creativity.
** Help
Feel free to contact us at any point if you need guidance.
- To learn Common Lisp, see [[https://nyxt-browser.com/learn-lisp]];
- [[https://github.com/atlas-engineer/nyxt/issues][Open up an issue on GitHub]];
- Find Nyxt on Libera IRC: =#nyxt=
- [[https://discord.gg/YXCk7gDKgJ][Nyxt's Discord]];
** Commit style
Ensure to isolate commits containing whitespace changes (including indentation)
or code movements as to avoid noise in the diffs.
Regarding commit messages, we follow the convention of prefixing the title with
the basename when there's a single modified file. For instance, for changes in
=source/mode/blocker.lisp= the commit message would look as per below:
#+begin_example
mode/blocker: Short description of the change
Further explanation.
#+end_example
** Branch management
Nyxt uses the following branches:
- =master= for development;
- =<feature-branches>= for working on particular features;
- =<integer>-series= to backport commits corresponding to specific major
versions.
Branch off from the target branch and rebase onto it right before merging as to
avoid merge conflicts.
A commit is said to be atomic when it builds and starts Nyxt successfully. At
times, for the sake of readability, it is wise to break the changes down to
smaller non-atomic commits. In that case, a merge commit is required (use merge
option =no-ff=). This guarantees that running =git bisect= with option
=--first-parent= only picks atomic commits, which streamlines the process.
Those with commit access may push trivial changes directly to the target branch.
** Programming conventions
The usual style guides by [[https://www.cs.umd.edu/~nau/cmsc421/norvig-lisp-style.pdf][Norvig & Pitman's Tutorial on Good Lisp Programming
Style]] and [[https://google.github.io/styleguide/lispguide.xml][Google Common Lisp Style Guide]] are advised.
For symbol naming conventions, see https://www.cliki.net/Naming+conventions.
Some of our conventions include:
- Prefer =first= and =rest= over =car= and =cdr=, respectively.
- Use =define-class= instead of =defclass=.
- Use =nyxt:define-package= for Nyxt-related pacakges. Notice that it features
default imports (e.g. =export-always=) and package nicknames (e.g. =alex=,
=sera=, etc.). Prefer =uiop:define-package= for general purpose packages.
- Export using =export-always= next to the symbol definition. This helps
prevent exports to go out-of-sync, or catch typos. Unlike =export=,
=export-always= saves you from surprises upon recompilation.
- When sensible, declaim the function types using =->=. Note that there is then
no need to mention the type of the arguments and the return value in the
docstring.
- Use the =maybe= and =maybe*= types instead of =(or null ...)= and =(or null
(array * (0)) ...)=, respectively.
- Use the =list-of= type for typed lists.
- Use =funcall*= to not error when function does not exist.
- Prefer classes over structs.
- Classes should be usable with just a =make-instance=.
- Slots classes should be formatted in the following way:
#+begin_src lisp
(slot-name
slot-value
...
:documentation "Foo.")
#+end_src
When =slot-value= is the only parameter specified then:
#+begin_src lisp
(slot-name slot-value)
#+end_src
- =customize-instance= is reserved for end users. Use
=initialize-instance :after= or =slot-unbound= to initialize the slots.
Set up the rest of the class in =customize-instance :after=. Bear in mind
that anything in this last method won't be customizable for the end user.
- Almost all files should be handled via the =nfiles= library.
- =(setf SLOT-WRITER) :after= is reserved for "watchers",
i.e. handlers that are run whenever the slot is set. The =:around= method is
not used by watchers, and thus the watcher may be overridden.
- We use the =%foo%= naming convention for special local variables.
- We suffix predicates with =-p=. Unlike the usual convention, we always use a
dash (i.e. =foo-p= over =foop=).
- Prefer the term =url= over =uri=.
- URLs should be of type =quri:uri=. If you need to manipulate a URL string, call
it =url-string=. In case the value contains a URL, but is not =quri:url=, use
=url-designator= and its =url= method to normalize into =quri:uri=.
- Paths should be of type =cl:pathname=.
Use =uiop:native-namestring= to "send" to OS-facing functions,
=uiop:ensure-pathname= to "receive" from OS-facing functions or to "trunamize".
- Prefer =handler-bind= over =handler-case=: when running from the REPL, this
triggers the debugger with a full stacktrace; when running the Nyxt binary,
all conditions are caught anyway.
- Do not handle the =T= condition, this may break everything. Handle =error=,
=serious-condition=, or exceptionally =condition= (for instance if you do not
control the called code, and some libraries subclass =condition= instead of
=error=).
- Dummy variables are called =_=.
- Prefer American spelling.
- Construct =define-command= requires a short one-line docstring without
newlines.
- Name keyword function parameters as follows =&key (var default-value
var-supplied-p)=.
# - Conversion functions =FROM->TO= or =->TO= for generic functions. The
# only one that comes to mind is =url= which does not follow this convention...
# - Blocking function should be prefixed with =wait-on-=.
# Local Variables:
# eval: (add-hook 'before-save-hook
# (lambda nil (if (fboundp 'org-make-toc)
# (org-make-toc)
# (message-box "Please install org-make-toc.")))
# nil
# t)
# End:
================================================
FILE: libraries/analysis/README.org
================================================
* Analysis
Analysis is a library that provides facilities to help analyze and
understand data. Listed below are the classes:
** Document
The document class represents a document. After creating a document,
you can perform several operations on it, some examples:
+ term count: how many times does a term appear in a document?
+ term frequency: how many times does a term appear divided by the
total number of words in the document?
** Document Collection
The document collection class represents a collection of documents. As
with a document, there are several operations available, some examples:
+ dictionary: which words appear in the document collection?
+ keywords: what are the important keywords in this document
collection?
** Document Vertex
The document vertex class represents a document that is part of a
graph. The edges slot of the document vertex class is used to store
edges of that particular vertex. The keys in the edges slot hash table
are the actual vertexes, and the values are the edge weights.
** Document Cluster
The document cluster class represents a document that is part of a
graph which will be clustered. It extends the document-vertex class
and adds support for a cluster tag and a list of neighbors. These
slots are useful for clustering algorithms.
================================================
FILE: libraries/analysis/analysis.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :analysis)
(export-always 'document)
(defclass document ()
((source :accessor source :initarg :source
:documentation "The source object for the document.")
(string-contents :initarg :string-contents :accessor string-contents)
(term-count-table :initform (make-hash-table :test #'equal)
:documentation "Contains a mapping of term ->
amount of times word appears in the document.")
(vector-data :accessor vector-data
:documentation "Vector representation of the document.")
(rank :accessor rank :documentation "Rank used for sorting.")
(tokens :accessor tokens)
(token-count :accessor token-count))
(:documentation "The document class represents a document. After
creating a document, you can perform several operations on it, some
examples:
+ term count: how many times does a term appear in a document?
+ term frequency: how many times does a term appear divided by the
total number of words in the document?"))
(defclass document-collection ()
((documents :initform () :initarg :documents :accessor documents))
(:documentation "The document collection class represents a
collection of documents. As with a document, there are several
operations available, some examples:
+ dictionary: which words appear in the document collection?
+ keywords: what are the important keywords in this document
collection?"))
(defmethod initialize-instance :after ((document document) &key)
(setf (tokens document) (word-tokenize (string-contents document)))
(setf (token-count document) (length (tokens document)))
(loop for token in (tokens document) do
(incf (gethash token (slot-value document 'term-count-table) 0))))
(defmethod term-count ((document document) term)
(gethash term (slot-value document 'term-count-table) 0))
(defmethod term-frequency ((document document) term)
"How often does the word exist in the document?"
(/ (term-count document term)
;; prevent division by zero for malformed documents
(max 1 (token-count document))))
(defmethod termp ((document document) term)
"Does the term exist in the document?"
(> (term-count document term) 0))
(defmethod add-document ((document-collection document-collection) document)
"Add a document to the document collection."
(push document (documents document-collection)))
(defun match-term (term)
(lambda (document)
(termp document term)))
(defmethod document-frequency ((document-collection document-collection) term)
(/ (count-if (match-term term) (documents document-collection))
(length (documents document-collection))))
(defmethod inverse-document-frequency ((document-collection document-collection) term)
(log (/ (length (documents document-collection))
(count-if (match-term term) (documents document-collection)))))
(defmethod term-frequency-inverse-document-frequency ((document document)
(document-collection document-collection)
term)
(* (term-frequency document term) (inverse-document-frequency document-collection term)))
(defmethod dictionary ((document document))
"Return a list of all of the words that appear in a document."
(loop for key being the hash-keys of (slot-value document 'term-count-table)
collect key))
(defmethod dictionary ((document-collection document-collection))
"Return a list of all of the words that appear in a document collection."
(let ((words (list)))
(loop for document in (documents document-collection)
do (alexandria:appendf words (tokens document)))
(remove-duplicates words :test #'equalp)))
(export-always 'keywords)
(defmethod keywords ((document document) &optional document-collection)
(if document-collection
(sort (loop for word in (dictionary document)
collect (cons word (term-frequency-inverse-document-frequency
document document-collection word)))
#'>
:key #'rest)
(sort (loop for word in (dictionary document)
collect (cons word (term-frequency document word)))
#'>
:key #'rest)))
(export-always 'extract-keywords)
(defun extract-keywords (text &key (limit 5))
"Extract keywords from a string of text."
(serapeum:take limit (keywords (make-instance 'analysis:document
:string-contents text))))
================================================
FILE: libraries/analysis/composite-sequence.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
;; Given the following sequence:
;; 1 2 3 4 5
;;
;; We would record the following sequences/outcomes.
;;
;; Chain length of 1:
;; 1 -> 2
;; 2 -> 3
;; 3 -> 4
;; 4 -> 5
;;
;; Chain length of 2:
;; 1 2 -> 3
;; 2 3 -> 4
;; 3 4 -> 5
;;
;; Chain length of 3:
;; 1 2 3 -> 4
;; 2 3 4 -> 5
;;
;; As can be seen above, the amount of subsequences within a given sequence is
;; equal to (- (length sequence) chain length).
(in-package :analysis)
(export-always 'sequence-model)
(defclass sequence-model (node)
()
(:documentation "The sequence-model class represents the root of a directed
graph. The edges represent possible sequences of events. It may help to
envision the graph as a finite state machine."))
(defclass node ()
((edges :accessor edges :initform (make-hash-table :test #'equal))))
(export-always 'element)
(defclass element-node (node)
((element :accessor element :initarg :element)
(occurrences
:accessor occurrences
:initform 0
:documentation "Number of times this element has appeared at the end of a
sequence.")))
(defmethod add-edge ((from-node node) (to-node node))
(alexandria:ensure-gethash (element to-node)
(edges from-node) to-node))
(defmethod list-edge-elements ((node node))
(mapcar #'element (alexandria:hash-table-values (edges node))))
(defmethod increment ((node node))
(incf (occurrences node)))
(export-always 'add-record)
(defmethod add-record ((model sequence-model) sequence)
(multiple-value-bind (list-but-last-element last-element) (serapeum:halves sequence)
(let ((leaf (alexandria:ensure-gethash list-but-last-element
(edges model)
(make-instance 'node))))
(increment (add-edge leaf (make-instance 'element-node :element (first last-element)))))))
(defmethod add-record-subsequence ((model sequence-model) sequence)
"Add a record for all subsequences. E.g. transform '(3 2 1)' into:
'(3 2 1), '(2 1), '(1)"
(loop while (> (length sequence) 1)
collect (add-record model sequence)
do (setf sequence (rest sequence))))
(export-always 'predict)
(defmethod predict ((model sequence-model) sequence)
(serapeum:and-let* ((leaf (gethash sequence (edges model)))
(edges (alexandria:hash-table-values (edges leaf))))
(first (sort edges #'> :key #'occurrences))))
(defmethod predict-subsequence-simple ((model sequence-model) sequence)
"Predict a sequence's next value based on all subsequence predictions. This is
a naive implementation which simply considers the amount of occurences without
regard to the weight of different chain lengths."
(let* ((subsequence-results
(loop while (> (length sequence) 1)
collect (let* ((leaf (gethash sequence (edges model)))
(edges (alexandria:hash-table-values (edges leaf))))
(first (sort edges #'> :key #'occurrences)))
do (setf sequence (rest sequence)))))
(first (sort subsequence-results #'> :key #'occurrences))))
================================================
FILE: libraries/analysis/data.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :analysis)
(defclass language-data ()
((stop-words
:initarg :stop-words
:accessor stop-words
:initform
(list "a" "able" "about" "above" "according" "accordingly" "across" "actually" "after"
"afterwards" "again" "against" "ain't" "all" "allow" "allows" "almost" "alone"
"along" "already" "also" "although" "always" "am" "among" "amongst" "an" "and"
"another" "any" "anybody" "anyhow" "anyone" "anything" "anyway" "anyways" "anywhere"
"apart" "appear" "appreciate" "appropriate" "are" "aren't" "around" "as" "a's" "aside"
"ask" "asking" "associated" "at" "available" "away" "awfully" "be" "became" "because" "become"
"becomes" "becoming" "been" "before" "beforehand" "behind" "being" "believe" "below" "beside"
"besides" "best" "better" "between" "beyond" "both" "brief" "but" "by" "came" "can" "cannot"
"cant" "can't" "cause" "causes" "certain" "certainly" "changes" "clearly" "c'mon" "co" "com"
"come" "comes" "concerning" "consequently" "consider" "considering" "contain" "containing"
"contains" "corresponding" "could" "couldn't" "course" "c's" "currently" "definitely" "described"
"despite" "did" "didn't" "different" "do" "does" "doesn't" "doing" "don" "done" "don't" "down"
"downwards" "during" "each" "edu" "eg" "eight" "either" "else" "elsewhere" "enough" "entirely"
"especially" "et" "etc" "even" "ever" "every" "everybody" "everyone" "everything" "everywhere"
"ex" "exactly" "example" "except" "far" "few" "fifth" "first" "five" "followed" "following" "follows"
"for" "former" "formerly" "forth" "four" "from" "further" "furthermore" "get" "gets" "getting" "given"
"gives" "go" "goes" "going" "gone" "got" "gotten" "greetings" "had" "hadn't" "happens" "hardly" "has"
"hasn't" "have" "haven't" "having" "he" "he'd" "he'll" "hello" "help" "hence" "her" "here" "hereafter"
"hereby" "herein" "here's" "hereupon" "hers" "herself" "he's" "hi" "him" "himself" "his" "hither"
"hopefully" "how" "howbeit" "however" "how's" "i" "i'd" "ie" "if" "ignored" "i'll" "i'm" "immediate"
"in" "inasmuch" "inc" "indeed" "indicate" "indicated" "indicates" "inner" "insofar" "instead" "into"
"inward" "is" "isn't" "it" "it'd" "it'll" "its" "it's" "itself" "i've" "just" "keep" "keeps" "kept"
"know" "known" "knows" "last" "lately" "later" "latter" "latterly" "least" "less" "lest" "let" "let's"
"like" "liked" "likely" "little" "look" "looking" "looks" "ltd" "mainly" "many" "may" "maybe" "me"
"mean" "meanwhile" "merely" "might" "more" "moreover" "most" "mostly" "much" "must" "mustn't" "my"
"myself" "name" "namely" "nd" "near" "nearly" "necessary" "need" "needs" "neither" "never" "nevertheless"
"new" "next" "nine" "no" "nobody" "non" "none" "noone" "nor" "normally" "not" "nothing" "novel" "now"
"nowhere" "obviously" "of" "off" "often" "oh" "ok" "okay" "old" "on" "once" "one" "ones" "only" "onto"
"or" "other" "others" "otherwise" "ought" "our" "ours" "ourselves" "out" "outside" "over" "overall" "own"
"particular" "particularly" "per" "perhaps" "placed" "please" "plus" "possible" "presumably" "probably"
"provides" "que" "quite" "qv" "rather" "rd" "re" "really" "reasonably" "regarding" "regardless" "regards"
"relatively" "respectively" "right" "s" "said" "same" "saw" "say" "saying" "says" "second" "secondly" "see"
"seeing" "seem" "seemed" "seeming" "seems" "seen" "self" "selves" "sensible" "sent" "serious" "seriously"
"seven" "several" "shall" "shan't" "she" "she'd" "she'll" "she's" "should" "shouldn't" "since" "six" "so"
"some" "somebody" "somehow" "someone" "something" "sometime" "sometimes" "somewhat" "somewhere" "soon" "sorry"
"specified" "specify" "specifying" "still" "sub" "such" "sup" "sure" "t" "take" "taken" "tell" "tends" "th"
"than" "thank" "thanks" "thanx" "that" "thats" "that's" "the" "their" "theirs" "them" "themselves" "then"
"thence" "there" "thereafter" "thereby" "therefore" "therein" "theres" "there's" "thereupon" "these" "they"
"they'd" "they'll" "they're" "they've" "think" "third" "this" "thorough" "thoroughly" "those" "though"
"three" "through" "throughout" "thru" "thus" "to" "together" "too" "took" "toward" "towards" "tried"
"tries" "truly" "try" "trying" "t's" "twice" "two" "un" "under" "unfortunately" "unless" "unlikely"
"until" "unto" "up" "upon" "us" "use" "used" "useful" "uses" "using" "usually" "value" "various" "very"
"via" "viz" "vs" "want" "wants" "was" "wasn't" "way" "we" "we'd" "welcome" "well" "we'll" "went" "were"
"we're" "weren't" "we've" "what" "whatever" "what's" "when" "whence" "whenever" "when's" "where"
"whereafter" "whereas" "whereby" "wherein" "where's" "whereupon" "wherever" "whether" "which" "while"
"whither" "who" "whoever" "whole" "whom" "who's" "whose" "why" "why's" "will" "willing" "wish" "with"
"within" "without" "wonder" "won't" "would" "wouldn't" "yes" "yet" "you" "you'd" "you'll" "your"
"you're" "yours" "yourself" "yourselves" "you've" "zero"))
(stop-words-lookup :accessor stop-words-lookup)))
(defmethod initialize-instance :after ((data language-data) &key)
(setf (stop-words-lookup data)
(loop with ht = (make-hash-table :test #'equal)
for stop in (stop-words data)
do (setf (gethash stop ht) t)
finally (return ht))))
(defparameter *language-data* (make-instance 'language-data))
================================================
FILE: libraries/analysis/dbscan.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :analysis)
;;; dbscan.lisp -- implementation of Density-based spatial clustering
;;; of applications with noise (DBSCAN) algorithm
(defclass document-cluster (document-vertex)
((cluster :accessor cluster :initform :noise)
(neighbors :accessor neighbors))
(:documentation "The document cluster class represents a document
that is part of a graph which will be clustered. It extends the
documenet-vertex class and adds support for a cluster tag and a list
of neighbors. These slots are useful for clustering algorithms."))
(defmethod clusters ((collection document-collection))
"Return a list of clusters. Each hash key represents a cluster, and
the hash value is the list of elements in that cluster.
Please note: this function is not responsible for computing the
clusters, only for returning the list of pre-tagged documents in
cluster lists."
(let ((result (make-hash-table)))
(loop for document in (documents collection)
do (push document (gethash (cluster document) result (list))))
result))
(defun get-cluster (cluster-label points)
"Return all matching points for a given cluster label."
(remove-if-not (lambda (i) (eq (cluster i) cluster-label)) points))
(defmethod distance ((vector-1 t) (vector-2 t))
"Return the Euclidean distance between two vectors."
(sqrt (loop for i across vector-1
for j across vector-2
sum (expt (- i j) 2))))
(defmethod distance ((document-a document-cluster) (document-b document-cluster))
(distance (vector-data document-a) (vector-data document-b)))
(defmethod generate-document-distance-vectors ((collection document-collection))
"Set the edge weights for all document neighbors (graph is fully connected)."
(with-accessors ((documents documents)) collection
(loop for document-a in documents
do (loop for document-b in documents
do (setf (gethash document-b (edges document-a))
(distance document-a document-b))))))
(defmethod dbscan ((collection document-collection) &key (minimum-points 3)
(epsilon 0.5))
"Minimum points refers to the minimum amount of points that must
exist in the neighborhood of a point for it to be considered a
core-point in a cluster. Epsilon refers to the distance between
two points for them to be considered neighbors."
(labels ((range-query (document)
"Return all points that have a distance less than epsilon."
(loop for vertex being the hash-keys of (edges document)
when (and (<= (gethash vertex (edges document)) epsilon)
(not (eq vertex document)))
collect vertex))
(core-point-p (point)
"Is a point a core-point?"
(<= minimum-points (length (range-query point))))
(cluster-match-p (point cluster)
"Check if a core point belongs to a cluster."
(intersection cluster (range-query point))))
;;; identify core points
(let* ((core-points (remove-if-not #'core-point-p (documents collection)))
(non-core-points (set-difference (documents collection) core-points)))
;;; assign labels to core points
(loop for point in core-points
with cluster-count = 0
do (loop named cluster-set
for i from 0 to cluster-count
;; point found cluster match, setf and break
when (cluster-match-p point (get-cluster i core-points))
do (setf (cluster point) i)
(return-from cluster-set)
;; point found no cluster-match, create new cluster
finally (setf (cluster point) (incf cluster-count))))
;;; assign labels to non-core points
(loop for point in non-core-points
for intersection = (intersection core-points (range-query point))
when intersection
do (setf (cluster point) (cluster (first intersection)))))))
================================================
FILE: libraries/analysis/document-vector.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :analysis)
;;; document-vector.lisp: transform a document into a vector
(defmethod word-count-vectorize ((document document) dictionary)
"Transform a document into a vector using word counts."
(let ((vector-data (make-array (length dictionary) :initial-element 0)))
(loop for word in dictionary
for index from 0 below (length vector-data)
do (setf (aref vector-data index) (term-count document word)))
(setf (vector-data document) vector-data)))
(defmethod tf-idf-vectorize ((document document) (collection document-collection) dictionary)
"Transform a document into a vector using tf-idf.
Definition: tf-idf: term frequency, inverse document frequency. How
often does a term a appear in a document as compared to all other
documents?"
(let ((vector-data (make-array (length dictionary) :initial-element 0)))
(loop for word in dictionary
for index from 0 below (length vector-data)
do (setf (aref vector-data index)
(term-frequency-inverse-document-frequency document collection word)))
(setf (vector-data document) vector-data)))
(defmethod tf-vectorize ((document document) dictionary)
"Transform a document into a vector using tf.
Definition: tf: term frequency. How often does a term appear in a
document?"
(let ((vector-data (make-array (length dictionary) :initial-element 0)))
(loop for word in dictionary
for index from 0 below (length vector-data)
do (setf (aref vector-data index)
(term-frequency document word)))
(setf (vector-data document) vector-data)))
(defmethod vectorize-documents ((document-collection document-collection) operation)
(let ((dictionary (dictionary document-collection)))
(loop for document in (documents document-collection)
do (funcall operation document dictionary))))
(defmethod word-count-vectorize-documents ((document-collection document-collection))
(vectorize-documents document-collection #'word-count-vectorize))
(defmethod tf-vectorize-documents ((document-collection document-collection))
"Definition: tf: term frequency. How often does a term appear in a
document?"
(vectorize-documents document-collection #'tf-vectorize))
(defmethod tf-idf-vectorize-documents ((document-collection document-collection))
"Definition: tf-idf: term frequency, inverse document frequency. How
often does a term appear in a document as compared to all other
documents?"
(vectorize-documents document-collection (lambda (document dictionary)
(tf-idf-vectorize document document-collection dictionary))))
================================================
FILE: libraries/analysis/package.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(uiop:define-package :analysis
(:use :cl)
(:import-from :serapeum #:export-always))
================================================
FILE: libraries/analysis/section.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :analysis)
;;; section.lisp -- given a document, automatically generate sections
(export-always 'extract-sections)
(defun extract-sections (text &key (epsilon 0.5))
"Extract the sections from a string of text. Epsilon refers to the
distance between two points for them to be considered related."
(labels ((average-distance (point points)
(/ (reduce #'+ points
:key (lambda (i) (distance (vector-data i)
(vector-data point))))
(length points))))
(let ((collection (make-instance 'document-collection)))
(loop for sentence in (sentence-tokenize text)
do (add-document collection
(make-instance 'document-cluster
:string-contents sentence)))
(tf-vectorize-documents collection)
(loop for document in (documents collection)
with cluster-index = 0
for cluster = (get-cluster cluster-index (documents collection))
do (if (and cluster (>= epsilon (average-distance document cluster)))
(setf (cluster document) cluster-index)
(setf (cluster document) (incf cluster-index))))
collection)))
================================================
FILE: libraries/analysis/stem.lisp
================================================
(in-package :analysis)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The software is completely free for any purpose, unless notes at
;; the head of the program text indicates otherwise (which is
;; rare). In any case, the notes about licensing are never more
;; restrictive than the BSD License.
;
;; In every case where the software is not written by me (Martin
;; Porter), this licensing arrangement has been endorsed by the
;; contributor, and it is therefore unnecessary to ask the contributor
;; again to confirm it.
;
;; The Porter Stemming Algorithm, somewhat mechanically hand translated to Common Lisp by
;; Steven M. Haflich smh@franz.com Feb 2002. Most of the inline comments refer to the
;; original C code. At the time of this translation the code passes the associated Porter
;; test files. See the function test at the end of this file.
;; This port is intended to be portable ANSI Common Lisp. However, it has only been
;; compiled and tested with Allegro Common Lisp. This code is offered in the hope it will
;; be useful, but with no warranty of correctness, suitability, usability, or anything
;; else. The C implementation from which this code was derived was not reentrant, relying
;; on global variables. This implementation corrects that. It is intended that a word to
;; be stemmed will be in a string with fill-pointer, as this is a natural result when
;; parsing user input, web scraping, whatever. If not, a string with fill-pointer is
;; created, but this is an efficiency hit and is here intended only for lightweight use or
;; testing. Using some resource mechanism on these strings would be a useful improvement,
;; whether here or in the calling code.
;; Postscript: When I contacted Martin Porter about this anachronism, he decided to fix
;; the C version to implement proper reentrancy. The CL version is now also served from
;; his central site. It should be functionally identical to this one, modulo the current
;; comment and a couple harmless formatting and comment changes.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This is the Porter stemming algorithm, coded up in ANSI C by the
;; author. It may be regarded as canonical, in that it follows the
;; algorithm presented in
;; Porter, 1980, An algorithm for suffix stripping, Program, Vol. 14,
;; no. 3, pp 130-137,
;; only differing from it at the points maked --DEPARTURE-- below.
;; See also http://www.tartarus.org/~martin/PorterStemmer
;; The algorithm as described in the paper could be exactly replicated
;; by adjusting the points of DEPARTURE, but this is barely necessary,
;; because (a) the points of DEPARTURE are definitely improvements, and
;; (b) no encoding of the Porter stemmer I have seen is anything like
;; as exact as this version, even with the points of DEPARTURE!
;; You can compile it on Unix with 'gcc -O3 -o stem stem.c' after which
;; 'stem' takes a list of inputs and sends the stemmed equivalent to
;; stdout.
;; The algorithm as encoded here is particularly fast.
;; Release 1
;; The main part of the stemming algorithm starts here. b is a buffer
;; holding a word to be stemmed. The letters are in b[k0], b[k0+1] ...
;; ending at b[k]. In fact k0 = 0 in this demo program. k is readjusted
;; downwards as the stemming progresses. Zero termination is not in fact
;; used in the algorithm.
;; Note that only lower case sequences are stemmed. Forcing to lower case
;; should be done before stem(...) is called.
;; cons(i) is TRUE <=> b[i] is a consonant.
;;; Common Lisp port Version 1.01
;;;
;;; Common Lisp port Version history
;;;
;;; 1.0 -- smh@franz.com Feb 2002
;;; initial release
;;;
;;; 1.01 -- smh@franz.com 25 Apr 2004
;;; step4 signalled error for "ion" "ions". Thanks to Jeff Heard
;;; for detecting this and suggesting the fix.
(defun consonantp (str i)
(let ((char (char str i)))
(cond ((member char '(#\a #\e #\i #\o #\u)) nil)
((eql char #\y)
(if (= i 0) t (not (consonantp str (1- i)))))
(t t))))
;; m() measures the number of consonant sequences between k0 and j. if c is
;; a consonant sequence and v a vowel sequence, and <..> indicates arbitrary
;; presence,
;; <c><v> gives 0
;; <c>vc<v> gives 1
;; <c>vcvc<v> gives 2
;; <c>vcvcvc<v> gives 3
;; ....
(defun m (str lim)
(let ((n 0)
(i 0))
(loop
(when (>= i lim) (return-from m n))
(if (not (consonantp str i)) (return nil))
(incf i))
(incf i)
(loop
(loop
(if (>= i lim) (return-from m n))
(if (consonantp str i) (return nil))
(incf i))
(incf i)
(incf n)
(loop
(if (>= i lim) (return-from m n))
(if (not (consonantp str i)) (return nil))
(incf i))
(incf i))))
;; vowelinstem() is TRUE <=> k0,...j contains a vowel
(defun vowelinstem (str)
(loop for i from 0 below (fill-pointer str)
unless (consonantp str i) return t))
;; doublec(j) is TRUE <=> j,(j-1) contain a double consonant.
(defun doublec (str i)
(cond ((< i 1) nil)
((not (eql (char str i) (char str (1- i)))) nil)
(t (consonantp str i))))
;; cvc(i) is TRUE <=> i-2,i-1,i has the form consonant - vowel - consonant
;; and also if the second c is not w,x or y. this is used when trying to
;; restore an e at the end of a short word. e.g.
;; cav(e), lov(e), hop(e), crim(e), but
;; snow, box, tray.
(defun cvc (str lim)
(decf lim)
(if (or (< lim 2)
(not (consonantp str lim))
(consonantp str (1- lim))
(not (consonantp str (- lim 2))))
(return-from cvc nil))
(if (member (char str lim) '(#\w #\x #\y)) (return-from cvc nil))
t)
;; ends(s) is TRUE <=> k0,...k ends with the string s.
(defun ends (str ending)
(declare (string str) (simple-string ending))
(let ((len1 (length str)) (len2 (length ending)))
(loop
for pa downfrom (1- len1) to 0
and pb downfrom (1- len2) to 0
unless (eql (char str pa) (char ending pb))
return nil
finally (return (when (< pb 0)
(decf (fill-pointer str) len2)
t)))))
;; setto(s) sets (j+1),...k to the characters in the string s, readjusting k.
(defun setto (str suffix)
(declare (string str) (simple-string suffix))
(loop for char across suffix
do (vector-push-extend char str)))
;; r(s) is used further down.
(defun r (str s sfp)
(if (> (m str (fill-pointer str)) 0)
(setto str s)
(setf (fill-pointer str) sfp)))
;; step1ab() gets rid of plurals and -ed or -ing. e.g.
;; caresses -> caress
;; ponies -> poni
;; ties -> ti
;; caress -> caress
;; cats -> cat
;; feed -> feed
;; agreed -> agree
;; disabled -> disable
;; matting -> mat
;; mating -> mate
;; meeting -> meet
;; milling -> mill
;; messing -> mess
;; meetings -> meet
(defun step1ab (str)
(when (eql (char str (1- (fill-pointer str))) #\s)
(cond ((ends str "sses") (incf (fill-pointer str) 2))
((ends str "ies") (setto str "i"))
((not (eql (char str (- (fill-pointer str) 2)) #\s)) (decf (fill-pointer str)))))
(cond ((ends str "eed") (if (> (m str (fill-pointer str)) 0)
(incf (fill-pointer str) 2)
(incf (fill-pointer str) 3)))
((let ((sfp (fill-pointer str)))
(if (or (ends str "ed")
(ends str "ing"))
(if (vowelinstem str)
t
(progn (setf (fill-pointer str) sfp)
nil))))
(cond ((ends str "at") (setto str "ate"))
((ends str "bl") (setto str "ble"))
((ends str "iz") (setto str "ize"))
((doublec str (1- (fill-pointer str)))
(unless (member (char str (1- (fill-pointer str))) '(#\l #\s #\z))
(decf (fill-pointer str))))
(t (if (and (= (m str (fill-pointer str)) 1)
(cvc str (fill-pointer str)))
(setto str "e"))))))
str)
;; step1c() turns terminal y to i when there is another vowel in the stem.
(defun step1c (str)
(let ((saved-fill-pointer (fill-pointer str)))
(when (and (ends str "y")
(vowelinstem str))
(setf (char str (fill-pointer str)) #\i))
(setf (fill-pointer str) saved-fill-pointer))
str)
;; step2() maps double suffices to single ones. so -ization ( = -ize plus
;; -ation) maps to -ize etc. note that the string before the suffix must give
;; m() > 0.
(defun step2 (str)
(let ((sfp (fill-pointer str)))
(when (> sfp 2)
(block nil
(case (char str (- (length str) 2))
(#\a (when (ends str "ational") (r str "ate" sfp) (return))
(when (ends str "tional") (r str "tion" sfp) (return)))
(#\c (when (ends str "enci") (r str "ence" sfp) (return))
(when (ends str "anci") (r str "ance" sfp) (return)))
(#\e (when (ends str "izer") (r str "ize" sfp) (return)))
(#\l (when (ends str "bli") (r str "ble" sfp) (return))
;; -DEPARTURE-
;; To match the published algorithm, replace prev line with
;; ((when (ends str "abli") (r str "able" sfp) (return))
(when (ends str "alli") (r str "al" sfp) (return))
(when (ends str "entli") (r str "ent" sfp) (return))
(when (ends str "eli") (r str "e" sfp) (return))
(when (ends str "ousli") (r str "ous" sfp) (return)))
(#\o (when (ends str "ization") (r str "ize" sfp) (return))
(when (ends str "ation") (r str "ate" sfp) (return))
(when (ends str "ator") (r str "ate" sfp) (return)))
(#\s (when (ends str "alism") (r str "al" sfp) (return))
(when (ends str "iveness") (r str "ive" sfp) (return))
(when (ends str "fulness") (r str "ful" sfp) (return))
(when (ends str "ousness") (r str "ous" sfp) (return)))
(#\t (when (ends str "aliti") (r str "al" sfp) (return))
(when (ends str "iviti") (r str "ive" sfp) (return))
(when (ends str "biliti") (r str "ble" sfp) (return)))
;; -DEPARTURE-
;; To match the published algorithm, delete next line.
(#\g (when (ends str "logi") (r str "log" sfp) (return)))))))
str)
;; step3() deals with -ic-, -full, -ness etc. similar strategy to step2.
(defun step3 (str)
(let ((sfp (fill-pointer str)))
(block nil
(case (char str (1- (length str)))
(#\e (when (ends str "icate") (r str "ic" sfp) (return))
(when (ends str "ative") (r str "" sfp) (return)) ; huh?
(when (ends str "alize") (r str "al" sfp) (return)))
(#\i (when (ends str "iciti") (r str "ic" sfp) (return)))
(#\l (when (ends str "ical") (r str "ic" sfp) (return))
(when (ends str "ful") (r str "" sfp) (return))) ; huh?
(#\s (when (ends str "ness") (r str "" sfp) (return))) ; huh?
)))
str)
;; step4() takes off -ant, -ence etc., in context <c>vcvc<v>.
(defun step4 (str)
(let ((sfp (fill-pointer str)))
(when (> sfp 2) ; Unnecessary?
(block nil
(case (char str (- sfp 2))
(#\a (if (ends str "al") (return)))
(#\c (if (ends str "ance") (return))
(if (ends str "ence") (return)))
(#\e (if (ends str "er") (return)))
(#\i (if (ends str "ic") (return)))
(#\l (if (ends str "able") (return))
(if (ends str "ible") (return)))
(#\n (if (ends str "ant") (return))
(if (ends str "ement") (return))
(if (ends str "ment") (return))
(if (ends str "ent") (return)))
(#\o (if (ends str "ion")
(let ((len (length str)))
(if (and (> len 0)
(let ((c (char str (1- len))))
(or (eql c #\s) (eql c #\t))))
(return)
(setf (fill-pointer str) sfp))))
(if (ends str "ou") (return))) ; takes care of -ous
(#\s (if (ends str "ism") (return)))
(#\t (if (ends str "ate") (return))
(if (ends str "iti") (return)))
(#\u (if (ends str "ous") (return)))
(#\v (if (ends str "ive") (return)))
(#\z (if (ends str "ize") (return))))
(return-from step4 str))
(unless (> (m str (fill-pointer str)) 1)
(setf (fill-pointer str) sfp)))
str))
;; step5() removes a final -e if m() > 1, and changes -ll to -l if m() > 1.
(defun step5 (str)
(let ((len (fill-pointer str)))
(if (eql (char str (1- len)) #\e)
(let ((a (m str len)))
(if (or (> a 1)
(and (= a 1)
(not (cvc str (1- len)))))
(decf (fill-pointer str))))))
(let ((len (fill-pointer str)))
(if (and (eql (char str (1- len)) #\l)
(doublec str (1- len))
(> (m str len) 1))
(decf (fill-pointer str))))
str)
;; In stem(p,i,j), p is a char pointer, and the string to be stemmed is from p[i] to p[j]
;; inclusive. Typically i is zero and j is the offset to the last character of a string,
;; (p[j+1] == '\0'). The stemmer adjusts the characters p[i] ... p[j] and returns the new
;; end-point of the string, k. Stemming never increases word length, so i <= k <= j. To
;; turn the stemmer into a module, declare 'stem' as extern, and delete the remainder of
;; this file.
(defun stem (str)
(let ((len (length str)))
;; With this line, strings of length 1 or 2 don't go through the
;; stemming process, although no mention is made of this in the
;; published algorithm. Remove the line to match the published
;; algorithm.
(if (<= len 2) (return-from stem str)) ; /*-DEPARTURE-*/
(if (typep str 'simple-string) ; Primarily for testing.
(setf str
(make-array len :element-type 'character
:fill-pointer len :initial-contents str)))
(step1ab str) (step1c str) (step2 str) (step3 str) (step4 str) (step5 str)
str))
#+never
(trace step1ab step1c step2 step3 step4 step5)
#+never
(defun test () ; Run against the distributed test files.
(with-open-file (f1 "voc.txt")
(with-open-file (f2 "output.txt")
(loop as w1 = (read-line f1 nil nil)
while w1
as w2 = (read-line f2 nil nil)
as w3 = (stem w1)
if (equal w2 w3)
count t into successes
else count t into failures
and do (format t "(stem ~s) => ~s wanted ~s~%" w1 w3 w2)
finally (progn (format t "sucesses ~d failures ~d~%" successes failures)
(return failures))))))
================================================
FILE: libraries/analysis/tests/tests.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(uiop:define-package :analysis/tests
(:use :cl :lisp-unit2)
(:import-from :analysis))
(in-package :analysis/tests)
(define-test test-single-length ()
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2))
(assert-equal (analysis::element (analysis::predict model '(1))) 2))
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2))
(analysis::add-record model '(2 3))
(analysis::add-record model '(2 3))
(assert-equal (analysis::element (analysis::predict model '(1))) 2))
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 3))
(analysis::add-record model '(1 3))
(assert-equal (analysis::element (analysis::predict model '(1))) 2))
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 3))
(analysis::add-record model '(1 3))
(analysis::add-record model '(1 3))
(assert-equal (analysis::element (analysis::predict model '(1))) 3))
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 3))
(analysis::add-record model '(1 2))
(assert-equal (analysis::element (analysis::predict model '(1))) 2)))
(define-test test-multiple-length ()
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2 3))
(assert-equal (analysis::element (analysis::predict model '(1 2))) 3))
;; Make sure the most temporally recent element is used
;; Fails in CCL.
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 3))
(assert-equal (analysis::element (analysis::predict model '(1 2))) 3))
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(assert-equal (analysis::element (analysis::predict model '(1 2))) 4))
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(assert-equal (analysis::element (analysis::predict model '(1 2))) 3)))
(define-test test-variable-length ()
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(assert-equal (analysis::element (analysis::predict model '(1 2))) 3))
(let ((model (make-instance 'analysis::sequence-model)))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2 4))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 2))
(analysis::add-record model '(1 3))
(analysis::add-record model '(1 3))
(analysis::add-record model '(1 3))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(analysis::add-record model '(1 2 3))
(assert-equal (analysis::element (analysis::predict model '(1))) 3)
(assert-equal (analysis::element (analysis::predict model '(1 2))) 3)))
================================================
FILE: libraries/analysis/text-rank.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :analysis)
;;; text-rank.lisp -- implementation of textrank algorithm
(defclass document-vertex (document)
((edges :accessor edges :initform (make-hash-table)
:documentation "The keys of the hash table represent the
edges, the values of the hash table represent the edge
weights."))
(:documentation "The document vertex class represents a document
that is part of a graph. The edges slot of the document vertex class
is used to store edges of that particular vertex. The keys in the
edges slot hash table are the actual vertexes, and the values are the
edge weights."))
(defmethod cosine-similarity ((document-a document) (document-b document))
"Calculate the cosine similarity between two vectors."
(flet ((vector-product (document-a document-b)
(loop for a across (vector-data document-a)
for b across (vector-data document-b)
sum (* a b)))
(vector-sum-root (document)
(sqrt (loop for i across (vector-data document)
sum (* i i))))
(vector-zero-p (document)
(every #'zerop (vector-data document))))
(if (or (vector-zero-p document-a) (vector-zero-p document-b))
0 ; if either vector is completely zero, they are dissimilar
(/ (vector-product document-a document-b)
(* (vector-sum-root document-a) (vector-sum-root document-b))))))
(defmethod generate-document-similarity-vectors ((collection document-collection))
"Set the edge weights for all document neighbors (graph is fully connected)."
(with-accessors ((documents documents)) collection
(loop for document-a in documents
do (loop for document-b in documents
do (setf (gethash document-b (edges document-a))
(cosine-similarity document-a document-b))))))
(defmethod text-rank ((collection document-collection) &key (epsilon 0.001)
(damping 0.85)
(initial-rank)
(iteration-limit 100))
"This method is used to calculate the text rankings for a document
collection. The `epsilon' is the maximum delta for a given node
rank change during an iteration to be considered convergent. The
`damping' is a factor utilized to normalize the data. The
`initial-rank' is the rank given to nodes before any
iterations. The `iteration-limit' is the amount of times the
algorithm may traverse the graph before giving up (if the algorithm
does not converge)."
(with-accessors ((documents documents)) collection
(unless (zerop (length documents))
(labels ((set-initial-rank ()
"Set the initial rank of all documents to a supplied
value OR 1/length of the documents."
(let ((initial-rank (or initial-rank (/ 1 (length documents)))))
(mapcar (lambda (document) (setf (rank document) initial-rank)) documents)))
(graph-neighbors (document)
"Return a list of neighbors. In a fully connected graph,
all nodes are a neighbor except for the node itself."
(remove document documents))
(graph-neighbor-edge-sum (document)
"Add up the edges of all neighbors of a given node."
(let ((sum (- (reduce #'+ (alexandria:hash-table-values (edges document))) 1)))
(if (> sum 0) sum 1)))
(document-similarity (document-a document-b)
(gethash document-b (edges document-a) 0))
(convergedp (previous-score current-score)
"Check if a delta qualifies for convergence."
(<= (abs (- previous-score current-score)) epsilon))
(calculate-rank (document)
"Calculate the rank of a document."
(loop for neighbor in (graph-neighbors document)
sum (/ (* damping (rank neighbor) (document-similarity document neighbor))
(graph-neighbor-edge-sum neighbor)))))
(set-initial-rank)
(loop with converged = nil
for iteration from 0 to iteration-limit until converged
do (setf converged t)
(loop for document in documents
for old-rank = (rank document)
for new-rank = (calculate-rank document)
do (setf (rank document) new-rank)
unless (convergedp old-rank new-rank)
do (setf converged nil)))))))
(export-always 'summarize-text)
(defun summarize-text (text &key (summary-length 3) (show-rank-p nil))
(let ((collection (make-instance 'document-collection)))
(loop for sentence in (sentence-tokenize text)
do (add-document collection
(make-instance 'document-vertex
:string-contents sentence)))
(tf-idf-vectorize-documents collection)
(generate-document-similarity-vectors collection)
(text-rank collection :iteration-limit 100)
(serapeum:take summary-length
(mapcar (if show-rank-p
(lambda (i) (cons (rank i) (string-contents i)))
#'string-contents)
(sort (documents collection) #'> :key #'rank)))))
================================================
FILE: libraries/analysis/tokenize.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :analysis)
(defun word-tokenize (string &key (remove-stop-words t) (stem nil) (down-case t) (alphabetic t))
"Split a string into a list of words."
(let* ((alpha-scanner (cl-ppcre:create-scanner "^[A-Za-z]*$"))
(tokens (str:split " " (str:collapse-whitespaces string)))
(tokens (if remove-stop-words
(delete-if (lambda (x) (gethash (string-downcase x) (stop-words-lookup *language-data*))) tokens)
tokens))
(tokens (if stem
(mapcar #'stem tokens)
tokens))
(tokens (if down-case
(mapcar #'string-downcase tokens)
tokens))
(tokens (if alphabetic
(delete-if-not (lambda (x) (cl-ppcre:scan alpha-scanner x)) tokens)
tokens)))
tokens))
(defun sentence-tokenize (string)
"Split a string into a list of sentences."
;; TODO: Use "\\p{Terminal_Punctuation}" regexp instead to catch all terminal
;; punctuation marks, including "," and ";"?
(remove "" (mapcar #'str:trim (cl-ppcre:split "[.!?]" string)) :test #'equal))
================================================
FILE: libraries/download-manager/engine.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :download-manager)
(defvar *default-download-directory* #p"~/Downloads/")
(defun default-download-directory ()
(let ((dir (ignore-errors (uiop:run-program '("xdg-user-dir" "DOWNLOAD")
:output '(:string :stripped t)))))
(when (or (null dir) (string= dir (uiop:getenv "HOME")))
(setf dir (uiop:getenv "XDG_DOWNLOAD_DIR")))
(unless dir
(setf dir *default-download-directory*))
dir))
(defun download-directory (&optional (directory (default-download-directory)))
"Return path to download directory.
Create it if it does not exist."
(unless directory
(setf directory (default-download-directory)))
(unless (string= "" (file-namestring directory))
(setf directory (format nil "~a/" (namestring directory))))
(truename (ensure-directories-exist directory)))
(defun ensure-unique-file (file)
"Return FILE if unique or suffix it with a number otherwise."
(loop with original-name = file
with suffix = 1
while (uiop:file-exists-p file)
do (setf file (make-pathname :defaults original-name
:name (format nil "~a.~d" (pathname-name (pathname original-name))
suffix)))
do (incf suffix))
(namestring (pathname file)))
(defvar *notifications* nil
"A channel which can be queried for download notifications.
The channel return value is a `download'.")
(defclass download ()
((requested-url
:accessor requested-url
:initarg :requested-url
:initform (quri:uri "")
:type quri:uri
:documentation "The URL that the user requested.
This may be different from the actual location of the download, e.g. in case of
automatic redirection. See RESOLVED-URL.")
(resolved-url
:accessor resolved-url
:initarg :resolved-url
:initform (quri:uri "")
:type quri:uri
:documentation "The actual source of the download.
This may be different from the URL the user requested, see REQUESTED-URL.")
(file
:accessor file
:initarg :file
:initform ""
:documentation "Path pointing to the local storage location of the
downloaded file.")
(downstream
:accessor downstream
:initarg :downstream
:initform nil
:documentation "The stream which can be read from to do the actual
download.")
(status
:accessor status
:initarg :status
;; TODO: String?
:initform nil)
(header
:accessor header
:initarg :header
:initform "")
(update-interval
:type alexandria:non-negative-real
:accessor update-interval
:initarg :update-interval
:initform 1.0
:documentation "Time in seconds after which a notification is sent to the
`*notifications*' channel.")
(last-update
:type alexandria:non-negative-real
:accessor last-update
:initarg :last-update
:initform 0.0
:documentation "Time in seconds when the last notification was sent.")
(finished-p
:accessor finished-p
:initform nil
:documentation "Non-nil if it has finished downloading.")
(bytes-fetched
:accessor bytes-fetched
:initform 0)
(bytes-last-update
:accessor bytes-last-update
:initform 0
:documentation "Bytes fetched when last `update' was called.")
(last-update-speed
:accessor last-update-speed
:initform 0
:documentation "Download speed in B/s when last `update' was called.")))
(defmethod filename ((download download))
"Return the full name of this downloaded file, as a string."
(format nil "~a" (file download)))
(defmethod temp-file ((download download))
"Return a file name suitable for unfinished
downloads."
(ensure-unique-file
(format nil "~a.part" (namestring (file download)))))
(defmethod bytes-total ((download download))
(let ((bytes (gethash "content-length"
(header download) 0)))
(if (stringp bytes) (parse-integer bytes) bytes)))
(defmethod progress ((download download))
"Return progress ratio.
When download is completed, return 1.0.
When progress cannot be computer (because bytes-total is unknown), return
(values 0 'unknown)."
(cond
((finished-p download) 1)
((if (> (bytes-total download) 0)
(/ (float (bytes-fetched download))
(float (bytes-total download)))
(values 0 'unknown)))))
(defmethod update ((download download))
"Send DOWNLOAD to the `notifications' channel.
Only send if last update was more than `update-interval' seconds ago."
(let* ((new-time (/ (get-internal-real-time) (float internal-time-units-per-second)))
(time-diff (- new-time (last-update download))))
(when (or (< (update-interval download) time-diff)
(finished-p download))
(calispel:! *notifications* download)
(setf (last-update-speed download)
(if (= 0 time-diff)
0
(round (/ (float (- (bytes-fetched download) (bytes-last-update download)))
time-diff))))
(setf (bytes-last-update download) (bytes-fetched download))
(setf (last-update download) new-time))))
(declaim (ftype (function (quri:uri &key (:directory (or string pathname))
(:proxy (or quri:uri null))
(:cookies (or string null))))
resolve))
(defun resolve (url &key
(directory (default-download-directory))
proxy
cookies)
"Start downloading URL concurrently and return a corresponding `download' object.
If DIRECTORY is nil, `default-download-directory' will be used. COOKIES can
specify a cookie jar as a string, which is useful for authenticated downloads.
PROXY is the full proxy address, e.g. \"socks5://127.0.0.1:9050\"."
(unless *notifications*
(setf *notifications* (make-instance 'calispel:channel)))
(let ((download (cache :url url
:directory (download-directory directory)
:cookies cookies
:proxy proxy)))
;; TODO: We just use bt:make-thread, no need for a channel... Unless need to
;; watch for unfinished downloads and warn the user before closing.
(bt:make-thread
(lambda ()
(fetch download))
:name "download-manager")
download))
================================================
FILE: libraries/download-manager/native.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
;;; Native Common Lisp download manager backend.
(in-package :download-manager)
(defmethod cache ((type (eql :url)) url &rest args)
(log:debug url args)
(apply #'locally-cache url args))
(defun parse-cookie-jar-string (cookie-jar-string host path)
"Host is for instance \"example.org\" and path is \"/foo/bar\"."
(cl-cookie:make-cookie-jar
:cookies (mapcar (lambda (c)
(cl-cookie:parse-set-cookie-header c host path))
(cl-ppcre:split " *; *" cookie-jar-string))))
(defun locally-cache (requested-url
&key
(directory (download-directory))
cookies
proxy)
(let* ((cookies-jar
(unless (str:emptyp cookies)
(parse-cookie-jar-string cookies (quri:uri-host requested-url)
(quri:uri-path requested-url)))))
(handler-case
(multiple-value-bind (stream status response-headers resolved-url)
(dex:get (quri:render-uri requested-url)
:want-stream t :force-binary t :keep-alive nil
:proxy (and proxy (quri:render-uri proxy)) :cookie-jar cookies-jar)
;; TODO: Allow caller to set the target filename?
(let* ((file (merge-pathnames
directory (extract-filename requested-url
response-headers))))
;; TODO: Touch file now to ensure uniqueness when actually downloading?
(make-instance 'download
:requested-url requested-url
:resolved-url (quri:uri resolved-url)
:header response-headers
:file file
:status status
:downstream stream)))
(error (c)
(error c)))))
(defmethod fetch ((download download)
&key (buffer-size 16)) ; Small for testing.
"Return the number of bytes fetched."
(let* ((buffer (make-array buffer-size :element-type '(unsigned-byte 8)))
;; Without `uiop:parse-native-namestring' `with-open-file' would fail
;; if `temp-file' had a wildcard.
(temp-file (uiop:parse-native-namestring (temp-file download))))
(with-open-file (output temp-file
:direction :output
:if-exists :supersede
:element-type '(unsigned-byte 8))
(log:info "Downloading ~s~% to ~s."
(or (ignore-errors (quri:url-decode (quri:render-uri (resolved-url download))))
(quri:render-uri (resolved-url download)))
(namestring (file download)))
(loop :for byte-position = (read-sequence buffer (downstream download))
:do (update download)
:when (plusp byte-position)
:do (incf (bytes-fetched download) byte-position)
:if (plusp byte-position)
:do (write-sequence buffer output :end byte-position)
:else :return nil))
;; TODO: Report something if bytes-fetched is not the same as bytes-total.
(setf (finished-p download) t)
(uiop:rename-file-overwriting-target temp-file
(ensure-unique-file
;; Same as above for `parse-native-namestring'.
(uiop:parse-native-namestring
(namestring (file download)))))
(update download)
(bytes-fetched download)))
(defun parse-http-header (header-entry)
"Return the alist of key-value paris in HEADER-ENTRY."
(mapcar (lambda (key-value)
(cl-ppcre:split "=" key-value))
;; TODO: Don't split at escaped or quoted semicolons?
(cl-ppcre:split " *; *" header-entry)))
(defun normalize-filename (filename)
"Remove surrounding quotes and return the basename as a string.
Return NIL if filename is not a string or a pathname."
(when (pathnamep filename)
(setf filename (namestring filename)))
(when (stringp filename)
(file-namestring (string-trim "\"" filename))))
(defun extract-filename (url &optional headers)
"Extract a filename to save the contents of a URL under."
;; See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields.
(or (normalize-filename
(second (assoc "filename"
(parse-http-header
(gethash "content-disposition" headers))
:test #'string=)))
(let ((basename
(ignore-errors (file-namestring (quri:uri-path url)))))
(if (or (null basename) (string= "" basename))
"index.html"
basename))))
================================================
FILE: libraries/download-manager/package.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(uiop:define-package :download-manager
(:use :cl)
(:export #:init
#:*notifications*
#:default-download-directory
#:download
#:resolved-url
#:requested-url
#:header
#:file
#:filename
#:bytes-fetched
#:bytes-total
#:progress
#:finished-p
#:last-update-speed
#:cache
#:resolve))
================================================
FILE: libraries/nasdf/install.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :nasdf)
(export-always 'nasdf-file)
(defclass nasdf-file (static-file)
((if-does-not-exist
:initform :error
:initarg :if-does-not-exist
:type (member :error nil)
:documentation "What to do when input file is missing:
- `:error': Signal an error.
- `nil': Skip it."))
(:documentation "Component type for files to install."))
(import 'nasdf-file :asdf-user)
(export-always 'nasdf-binary-file)
(defclass nasdf-binary-file (nasdf-file) ()
(:documentation "Component type for executables to install."))
(import 'nasdf-binary-file :asdf-user)
(export-always 'nasdf-library-file)
(defclass nasdf-library-file (nasdf-binary-file) ()
(:documentation "Component type for libraries (shared objects) to install."))
(import 'nasdf-library-file :asdf-user)
(export-always 'nasdf-desktop-file)
(defclass nasdf-desktop-file (nasdf-file) ()
(:documentation "Component type for XDG .desktop files to install."))
(import 'nasdf-desktop-file :asdf-user)
(export-always 'nasdf-appdata-file)
(defclass nasdf-appdata-file (nasdf-file) ()
(:documentation "Component type for Appdata files to install."))
(import 'nasdf-appdata-file :asdf-user)
(export-always 'nasdf-icon-scalable-file)
(defclass nasdf-icon-scalable-file (nasdf-file) ()
(:documentation "Component type for the SVG icon."))
(import 'nasdf-icon-scalable-file :asdf-user)
(export-always 'nasdf-icon-directory)
(defclass nasdf-icon-directory (nasdf-file)
((asdf/interface::type :initform "png")) ; TODO: Is there a standard way to access the type?
(:documentation "Component type for directory containing icon files to install.
File of type `type' are looked for.
The last number found in the file name is used to install the icon in the right directory."))
(import 'nasdf-icon-directory :asdf-user)
;; TODO: Is it possible to list all files targetted by an ASDF system?
(export-always 'nasdf-source-directory)
(defclass nasdf-source-directory (nasdf-file)
((exclude-subpath
:initform '()
:type (or null (cons string *))
:accessor exclude-subpath
:initarg :exclude-subpath
:documentation "Subpath to exclude from installation.
Subpaths are relative to the component, so
(:nasdf-source-directory \"foo\" :exclude-subpath (\"bar\"))
means that foo/bar is excluded, but foo/baz is not.
If subpath is a directory, then all its subpaths are excluded as well.")
(exclude-types
:initform '("fasl")
:type (or null (cons string *))
:accessor exclude-types
:initarg :exclude-types
:documentation "Pattern of files to exclude when not using Git."))
(:documentation "Directory of Common Lisp source files.
Subdirectory is included.
Git is used to list the tracked files -- untracked files will be ignored.
If Git is not found, fall back to copying everything except files of type in `exclude-types'.
Destination directory is given by the `dest-source-dir' generic function."))
(import 'nasdf-source-directory :asdf-user)
(defun nil-pathname-p (pathname)
"Return non-nil if PATHNAME is `*nil-pathname*' or nil."
(the (values boolean &optional)
(or (null pathname)
(pathname-equal pathname *nil-pathname*))))
(defun basename (pathname) ; From nfiles.
"Return the basename, that is:
- if it's a directory, the name of the directory,
- if it's a file, the name of the file including its type (extension),
- nil if it's a nil-pathname (#p\"\")."
(if (nil-pathname-p pathname)
nil ; TODO: Shouldn't we return #p"" instead?
(first (last (pathname-directory
;; Ensure directory _after_ truenamizing, otherwise if
;; non-directory file exists it may not yield a directory.
(ensure-directory-pathname
(ensure-pathname pathname :truenamize t)))))))
(defun path-from-env (environment-variable default)
(let ((env (getenv environment-variable)))
(if env
(ensure-directory-pathname env)
default)))
(defun relative-path-from-env (environment-variable default)
(let ((env (getenv environment-variable)))
(if env
(relativize-pathname-directory (ensure-directory-pathname env))
default)))
;; We use `defparameter' so that paths are re-computed on system reload.
(export-always '*destdir*)
(defparameter *destdir* (if (getenv "DESTDIR")
(ensure-directory-pathname (getenv "DESTDIR"))
#p"/"))
(export-always '*prefix*)
(defparameter *prefix* (merge-pathnames* (relative-path-from-env "PREFIX" #p"usr/local/")
*destdir*))
(export-always '*datadir*)
(defparameter *datadir* (path-from-env "DATADIR" (merge-pathnames* "share/" *prefix*)))
(export-always '*bindir*)
(defparameter *bindir* (path-from-env "BINDIR" (merge-pathnames* "bin/" *prefix*)))
(export-always '*libdir*)
(defparameter *libdir* (path-from-env "LIBDIR" (merge-pathnames* "lib/" *prefix*)))
(export-always 'libdir)
(defmethod libdir ((component nasdf-library-file))
(let ((name (primary-system-name (component-system component))))
(ensure-directory-pathname (merge-pathnames* name *libdir*))))
(export-always '*dest-source-dir*)
(defvar *dest-source-dir* (path-from-env "NASDF_SOURCE_PATH" *datadir*)
"Root of where the source will be installed.
Final path is resolved in `dest-source-dir'.")
(export-always 'dest-source-dir)
(defmethod dest-source-dir ((component nasdf-source-directory))
"The directory into which the source is installed."
(let ((name (primary-system-name (component-system component))))
(ensure-directory-pathname
(merge-pathnames* name *dest-source-dir*))))
(export-always '*chmod-program*)
(defvar *chmod-program* "chmod")
(export-always '*chmod-executable-arg*)
(defvar *chmod-executable-arg* "+x")
(export-always 'make-executable)
(defun make-executable (file)
"Does nothing if files does not exist."
;; TODO: Use iolib/os:file-permissions instead of chmod? Too verbose?
(when (file-exists-p file)
(run-program (list *chmod-program* *chmod-executable-arg* (native-namestring file)))))
(export-always 'install-file)
(defun install-file (file dest)
"Like `copy-file' but ensures all parent directories are created if necessary."
(ensure-all-directories-exist
(list (directory-namestring dest)))
(copy-file file dest))
(defmethod perform ((op compile-op) (c nasdf-file)) ; REVIEW: load-op?
(loop for input in (input-files op c)
for output in (output-files op c)
do (if (or (file-exists-p input)
(slot-value c 'if-does-not-exist))
(progn
(install-file input output)
;; (format *error-output* "~&; installing file~%; ~s~%; to~%; ~s~%" source dest) ; Too verbose?
(logger "installed ~s" output))
(logger "skipped ~s" output)))
nil)
(defmethod output-files ((op compile-op) (c nasdf-file))
(values (list (merge-pathnames* (pathname-name (component-name c))
*prefix*))
t))
(defmethod output-files ((op compile-op) (c nasdf-binary-file))
(values (list (merge-pathnames* (basename (component-name c)) *bindir*))
t))
(defmethod perform ((op compile-op) (c nasdf-binary-file))
(call-next-method)
(mapc #'make-executable (output-files op c))
nil)
(defmethod output-files ((op compile-op) (c nasdf-library-file))
(values (list (merge-pathnames* (basename (component-name c)) (libdir c)))
t))
(defmethod output-files ((op compile-op) (c nasdf-desktop-file))
(values (list (merge-pathnames* (merge-pathnames*
(basename (component-name c))
"applications/")
*datadir*))
t))
(defmethod output-files ((op compile-op) (c nasdf-appdata-file))
(values (list (merge-pathnames* (merge-pathnames*
(basename (component-name c))
"metainfo/")
*datadir*))
t))
(defmethod output-files ((op compile-op) (c nasdf-icon-scalable-file))
(values (list (merge-pathnames* (merge-pathnames*
(basename (component-name c))
"icons/hicolor/scalable/apps/")
*datadir*))
t))
;; TODO Moving png icons to assets/icons would simplify their handling.
(defun scan-last-number (path)
"Return the last number found in PATH.
Return NIL is there is none."
(let ((result (block red
(reduce (lambda (&optional next-char result)
(if (parse-integer (string next-char) :junk-allowed t)
(cons next-char result)
(if result
(return-from red result)
result)))
(native-namestring path)
:initial-value '()
:from-end t))))
(when result
(coerce result 'string))))
(defmethod input-files ((op compile-op) (c nasdf-icon-directory))
"Return all files of NASDF-ICON-DIRECTORY `type' in its directory.
File must contain a number in their path."
(let ((result (remove-if (complement #'scan-last-number)
(directory-files (component-pathname c)
(strcat "*." (file-type c))))))
(let* ((dimensions (mapcar #'scan-last-number result))
(dups (set-difference dimensions
(remove-duplicates dimensions)
:test 'string=)))
(if (= 0 (length dups))
result
(error "Directory contains icons with duplicate dimensions: ~a" dups)))))
(defmethod output-files ((op compile-op) (c nasdf-icon-directory))
(let ((name (primary-system-name (component-system c))))
(values
(mapcar (lambda (path)
(let ((icon-size (scan-last-number path)) )
(format nil "~a/icons/hicolor/~ax~a/apps/~a.png"
*datadir*
icon-size icon-size
name)))
(input-files op c))
t)))
(defun file-excluded-type (file exclude-types)
(member (pathname-type file) exclude-types :test 'equalp))
(defun list-directory (directory &key exclude-subpath (exclude-types '("fasl")))
(let ((result '()))
(collect-sub*directories
(ensure-directory-pathname directory)
(constantly t)
(lambda (dir)
(notany (lambda (exclusion)
(string-suffix-p (basename dir) exclusion))
(mapcar #'basename exclude-subpath)))
(lambda (subdirectory)
(setf result (append result
(remove-if
(lambda (file) (file-excluded-type file exclude-types))
(directory-files subdirectory))))))
result))
(export-always 'copy-directory)
(defun copy-directory (source destination &key exclude-subpath (exclude-types '("fasl")) verbose-p) ; REVIEW: Unused, but seem quite useful.
"Copy the content (the file tree) of SOURCE to DESTINATION."
(when verbose-p
(logger "copy ~s/* inside ~s." source destination))
(mapc (lambda (file)
(unless (member (pathname-type file) exclude-types :test 'equalp)
(let ((destination-file
(merge-pathnames*
(subpathp file (ensure-directory-pathname source))
(ensure-pathname destination :truenamize t :ensure-directory t))))
(install-file file destination-file))))
(list-directory source :exclude-subpath exclude-subpath
:exclude-types exclude-types)))
(defmethod input-files ((op compile-op) (component nasdf-source-directory))
"Return all files of NASDF-SOURCE-DIRECTORY."
(with-current-directory ((system-source-directory (component-system component)))
(list-directory (component-pathname component)
:exclude-subpath (exclude-subpath component)
:exclude-types (exclude-types component))))
(defmethod output-files ((op compile-op) (component nasdf-source-directory))
(let ((root (system-source-directory (component-system component))))
(values
(mapcar (lambda (path)
(merge-pathnames* (subpathp path root) (dest-source-dir component)))
(input-files op component))
t)))
(export-always 'nasdf-source-file)
(defclass nasdf-source-file (nasdf-file) ()
(:documentation "Common Lisp source files.
Destination directory is given by the `dest-source-dir' generic function."))
(import 'nasdf-source-file :asdf-user)
(defmethod dest-source-dir ((component nasdf-source-file)) ; TODO: Factor with other method?
"The directory into which the source is installed."
(let ((name (primary-system-name (component-system component))))
(ensure-directory-pathname
(merge-pathnames* name *dest-source-dir*))))
(defmethod output-files ((op compile-op) (c nasdf-source-file))
(values (list (merge-pathnames* (basename (component-name c)) (dest-source-dir c)))
t))
================================================
FILE: libraries/nasdf/log.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :nasdf)
;; TODO: Use full-fledged logging facility?
;; Maybe we want to keep this dependency-free though...
(defvar *log-prefix* "; ")
(defun logger (control-string &rest format-arguments)
"Like `format' but assumes `*error-output*' as a stream and ensures fresh lines."
(let ((*standard-output* *error-output*))
(fresh-line)
(princ *log-prefix*)
(apply #'format t control-string format-arguments)
(fresh-line)))
================================================
FILE: libraries/nasdf/nasdf.asd
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(defsystem "nasdf"
:version "0.1.8"
:author "Atlas Engineer LLC"
:description "ASDF helpers for system setup, testing and installation."
:license "BSD 3-Clause"
:components ((:file "package")
(:file "log")
(:file "nasdf")
(:file "install")
(:file "systems")
(:file "tests")))
================================================
FILE: libraries/nasdf/nasdf.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :nasdf)
(defmacro export-always (symbols &optional (package nil package-supplied?))
"Like `export', but also evaluated at compile time."
`(eval-when (:compile-toplevel :load-toplevel :execute)
(export ,symbols ,@(and package-supplied? (list package)))))
(defun env-true-p (env-variable)
(let ((value (getenv env-variable)))
(or (string-equal "true" value)
(string-equal "yes" value)
(string-equal "on" value)
(string-equal "1" value))))
================================================
FILE: libraries/nasdf/package.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
#+sb-package-locks
(eval-when (:compile-toplevel :load-toplevel :execute)
(when (find-package :nasdf)
(sb-ext:unlock-package :nasdf)))
(uiop:define-package :nasdf
(:use #:cl #:uiop #:asdf)
(:documentation "ASDF helpers for system setup, testing and installation.
A system that installs files:
(defsystem \"my-project/install\"
:defsystem-depends-on (\"nasdf\")
:depends-on (alexandria)
:components ((:nasdf-desktop-file \"assets/my-project.desktop\")
(:nasdf-icon-directory \"assets/\")
(:nasdf-binary-file \"my-project\")
(:nasdf-library-file \"libraries/web-extensions/libmy.so\"
:if-does-not-exist nil)
(:nasdf-source-directory \"source\")
(:nasdf-source-directory \"nasdf\")
(:nasdf-source-directory \"libraries\"
:exclude-subpath (\"web-extensions\") ; Do not install this non-Lisp source.
:exclude-types (\"o\" \"c\" \"h\" ; C code and artifacts.
\"fasl\"))))
A test system:
(defsystem \"my-project/tests\"
:defsystem-depends-on (\"nasdf\")
:class :nasdf-test-system
:depends-on (alexandria lisp-unit2)
:components ((:file \"tests\"))
:test-suite-args (:package :my-project/tests))"))
#+sb-package-locks
(sb-ext:lock-package :nasdf)
================================================
FILE: libraries/nasdf/readme.org
================================================
#+TITLE: NASDF
NASDF is an ASDF extension providing utilities to ease system setup, testing and
installation.
* Features
- Test suite helpers.
- Installation helpers such as handling of icons or desktop files.
See [[file:package.lisp]] for more details.
* Environment variables
NASDF exposes the following environment variables for convenience:
- =NASDF_SOURCE_PATH= :: See =nasdf:*dest-source-dir*=.
- =NASDF_USE_LOGICAL_PATHS= :: Allow non-expanded logical pathnames in system
pathnames.
This is particularly useful when shipping the source.
Disable it if your tooling (e.g. SLIME) encounters issues to find the
definition of symbols.
See =asdf:nasdf-file=.
All boolean environment variables try to be smart enough to understand what you
mean; for instance both =on= and =true= are valid values to enable the feature.
================================================
FILE: libraries/nasdf/systems.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :nasdf)
(export-always 'nasdf-system)
(defclass nasdf-system (system) ()
(:documentation "Extended ASDF system.
It enables features such as:
- Togglable logical-pathnames depending on NASDF_USE_LOGICAL_PATHS.
- Executable dependencies are made immutable for ASDF to prevent accidental reloads."))
(import 'nasdf-system :asdf-user)
(defmethod perform :before ((o image-op) (c nasdf-system))
"Perform some last minute tweaks to the final image.
- Register immutable systems to prevent compiled images from
trying to recompile the application and its dependencies.
See `:*immutable-systems*'.
- If on SBCL, include `sb-sprof', the statistical profiler, since it's one of
the few modules that's not automatically included in the image."
#+sbcl
(require :sb-sprof)
(map () 'register-immutable-system (already-loaded-systems)))
(defun set-new-translation (host logical-directory
root-directory
&optional (translated-directory (string-downcase (substitute #\/ #\; logical-directory))))
"Add default translations for LOGICAL-DIRECTORY (e.g. \"foo;bar;\") in HOST.
Default translations:
- FASL files are expanded as usual with `apply-output-translations' (should default to the ASDF cache).
- Other files are expanded to their absolute location.
This effectively makes the logical pathname behave as if it had been a physical
pathname."
(let* ((logical-directory (if (string-suffix-p logical-directory ";")
logical-directory
(strcat logical-directory ";")))
(logical-path (strcat host ":" logical-directory "**;*.*.*"))
(logical-fasl-path (strcat host ":" logical-directory "**;*.fasl.*"))
(path-translation (ensure-pathname
(subpathname* root-directory
translated-directory)
:ensure-directory t
:wilden t))
(fasl-translation (ensure-pathname
(apply-output-translations
(subpathname* root-directory
translated-directory))
:wilden t)))
(if (ignore-errors (logical-pathname-translations host))
(flet ((set-alist (key value)
(let ((pair (assoc key (logical-pathname-translations host)
:key #'namestring
:test #'string-equal)))
(if pair
(setf (rest pair) (list value))
(push (list key value)
(logical-pathname-translations host))))))
(set-alist logical-path path-translation)
(set-alist logical-fasl-path fasl-translation)
;; Return this for consistency:
(list (list logical-fasl-path fasl-translation)
(list logical-path path-translation)))
(setf (logical-pathname-translations host)
;; WARNING: fasl path must come first as it's more specific.
(list (list logical-fasl-path fasl-translation)
(list logical-path path-translation))))))
(defun logical-word-or-lose (word) ; From `sb-impl::logical-word-or-lose'.
(declare (string word))
(when (string= word "")
(error 'namestring-parse-error
:complaint "Attempted to treat invalid logical hostname ~
as a logical host:~% ~S"
:args (list word)
:namestring word :offset 0))
(let ((word (string-upcase word)))
(dotimes (i (length word))
(let ((ch (schar word i)))
(unless (and (typep ch 'standard-char)
(or (alpha-char-p ch) (digit-char-p ch) (char= ch #\-)))
(error 'namestring-parse-error
:complaint "logical namestring character which ~
is not alphanumeric or hyphen:~% ~S"
:args (list ch)
:namestring word :offset i))))
(coerce word 'string)))
(defun parse-logical-pathname (pathname)
"Return two values:
- the host;
- the directory."
(let* ((name (namestring pathname))
(pos (position #\: name)))
(when pos
(let ((host (subseq name 0 (position #\: name))))
(when (ignore-errors (logical-word-or-lose host))
(values host
(subseq name (1+ (position #\: name)))))))))
(defmethod component-pathname ((system nasdf-system))
"If NASDF_USE_LOGICAL_PATHS environment variable is set, use logical path source
location, otherwise use the translated path.
Tools such as Emacs (SLIME and SLY) may fail to make use of logical paths, say,
to go to the compilation error location."
(let ((path (call-next-method)))
(when path
(let ((final-path (let ((host (parse-logical-pathname path)))
(if host
(progn
(set-new-translation host
(subseq (namestring path) (1+ (length host)))
(system-source-directory system))
;; The #p reader macro expands to logical
;; pathnames only if the host is already
;; defined, which may not be the case at this
;; point, so we remake the pathname.
(make-pathname :defaults path))
path))))
(if (env-true-p "NASDF_USE_LOGICAL_PATHS")
final-path
(translate-logical-pathname final-path))))))
(defclass nyxt-renderer-system (system) ()
(:documentation "Specialized systems for Nyxt with renderer dependency.
The renderer is configured from NYXT_RENDERER or `*nyxt-renderer*'."))
(import 'nyxt-renderer-system :asdf-user)
(export '*nyxt-renderer*)
(defvar *nyxt-renderer* (or (getenv "NYXT_RENDERER")
"gi-gtk"))
(defmethod component-depends-on ((o prepare-op) (c nyxt-renderer-system))
`((load-op ,(format nil "nyxt/~a-application" *nyxt-renderer*))
,@(call-next-method)))
================================================
FILE: libraries/nasdf/tests.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :nasdf)
(export-always 'nasdf-test-system)
(defclass nasdf-test-system (nasdf-system)
((test-suite-args
:initform nil
:initarg :test-suite-args
:reader test-suite-args
:documentation "Arguments passed to `lisp-unit2:run-tests'."))
(:documentation "Specialized system that runs `lisp-unit2' test suites, whose parameters are
specified by the `test-suite-args' slot."))
(import 'nasdf-test-system :asdf-user)
(defmethod perform ((op test-op) (c nasdf-test-system))
(destructuring-bind (&key package tags exclude-tags &allow-other-keys) (test-suite-args c)
(let ((output (symbol-call
:lisp-unit2 :run-tests
:package package
:tags tags
:run-contexts (find-symbol* :with-summary-context :lisp-unit2)))))))
(export-always 'print-benchmark)
(defun print-benchmark (benchmark-results)
(labels ((rat->float (num)
(if (integerp num) num (float num)))
(print-times (entry)
(let ((title (first entry))
(attr (rest entry)))
(unless (or (member (symbol-name title)
'("RUN-TIME" "SYSTEM-RUN-TIME"))
(and (member (symbol-name title)
'("PAGE-FAULTS" "EVAL-CALLS")
:test #'string=)
(zerop (getf attr :average))))
(format t " ~a: ~,9t~a"
(string-downcase title)
(rat->float (getf attr :average)))
(format t "~32,8t[~a, ~a]"
(rat->float (getf attr :minimum))
(rat->float (getf attr :maximum)))
(format t "~56,8t(median ~a, deviation ~a, total ~a)"
(rat->float (getf attr :median))
(rat->float (getf attr :deviation))
(rat->float (getf attr :total)))
(format t "~%")))))
(dolist (mark benchmark-results)
(format t "~a (~a sample~:p):~%" (first mark)
(getf (rest (second mark)) :samples))
(mapc #'print-times (rest mark)))))
================================================
FILE: libraries/password-manager/package.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(uiop:define-package :password
(:use :cl)
(:import-from :nclasses #:define-class)
(:import-from :serapeum #:export-always))
(eval-when (:compile-toplevel :load-toplevel :execute)
(trivial-package-local-nicknames:add-package-local-nickname :sera :serapeum :password))
================================================
FILE: libraries/password-manager/password-keepassxc.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :password)
(define-class keepassxc-interface (password-interface)
((executable (pathname->string (sera:resolve-executable "keepassxc-cli")))
(password-file
:documentation "The path to the KeePass password database.")
(key-file
nil
:type (or null string pathname)
:documentation "The key file for `password-file'.")
(master-password
""
:type string
:documentation "The password to the `password-file'.")
(yubikey-slot
nil
:documentation "Yubikey slot to unlock the `password-file'.")
(entries-cache
nil
:type list
:export nil
:documentation "The cache to speed the entry listing up."))
(:export-class-name-p t)
(:export-accessor-names-p t))
(push 'keepassxc-interface *interfaces*)
(defmethod list-passwords ((password-interface keepassxc-interface))
(or (entries-cache password-interface)
(let* ((st (make-string-input-stream (master-password password-interface)))
(output (execute password-interface
(append (list "ls" "-Rf") ; Recursive flattened.
(when (key-file password-interface)
(list "-k" (uiop:native-namestring (key-file password-interface))))
(when (yubikey-slot password-interface)
(list "-y" (yubikey-slot password-interface)))
(list (password-file password-interface)))
:input st :output '(:string :stripped t))))
(setf (entries-cache password-interface)
(remove-if (alexandria:curry #'str:ends-with-p "/") (sera:lines output))))))
(defmethod clip-password ((password-interface keepassxc-interface) &key password-name service)
(declare (ignore service))
(with-input-from-string (st (master-password password-interface))
(execute password-interface
(append
(list "clip")
(when (key-file password-interface)
(list "-k" (uiop:native-namestring (key-file password-interface))))
(when (yubikey-slot password-interface)
(list "-y" (yubikey-slot password-interface)))
(list (password-file password-interface) password-name))
:input st
:wait-p nil)))
(defmethod clip-username ((password-interface keepassxc-interface) &key password-name service)
(declare (ignore service))
(with-input-from-string (st (master-password password-interface))
(execute password-interface
(append (list "clip" "--attribute" "username")
(when (key-file password-interface)
(list "-k" (uiop:native-namestring (key-file password-interface))))
(when (yubikey-slot password-interface)
(list "-y" (yubikey-slot password-interface)))
(list (password-file password-interface) password-name))
:input st
:wait-p nil)))
(defmethod save-password ((password-interface keepassxc-interface)
&key password-name username password service)
(declare (ignore service))
;; This is to force entries re-fetching the next time we need passwords.
(setf (entries-cache password-interface) nil)
(with-input-from-string (st (format nil "~a~C~a"
(master-password password-interface)
#\newline password))
(execute password-interface
(append (list "add" "--username" username
"--password-prompt" (password-file password-interface))
(when (key-file password-interface)
(list "-k" (uiop:native-namestring (key-file password-interface))))
(when (yubikey-slot password-interface)
(list "-y" (yubikey-slot password-interface)))
(list (if (str:emptyp password-name)
"--generate"
password-name)))
:input st)))
(defmethod password-correct-p ((password-interface keepassxc-interface))
(handler-case
(list-passwords password-interface)
(uiop/run-program:subprocess-error ()
nil)))
================================================
FILE: libraries/password-manager/password-pass.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :password)
(define-class password-store-interface (password-interface)
((executable (pathname->string (sera:resolve-executable "pass")))
(sleep-timer (or (uiop:getenv "PASSWORD_STORE_CLIP_TIME") 45))
(password-directory (or (uiop:getenv "PASSWORD_STORE_DIR")
(format nil "~a/.password-store" (uiop:getenv "HOME")))
:type string
:reader password-directory))
(:export-class-name-p t)
(:export-accessor-names-p t))
(push 'password-store-interface *interfaces*)
(defmethod list-passwords ((password-interface password-store-interface))
(let ((directory (uiop:truename* (uiop:parse-native-namestring
(password-directory password-interface)))))
(when directory
;; Special care must be taken for symlinks. Say `~/.password-store/work`
;; points to `~/work/pass`, would we follow symlinks, we would not be able to
;; truncate `~/.password-store/` in `~/work/pass/some/password.gpg`. Because
;; of this, we don't follow symlinks.
(let* ((raw-list (uiop:directory*
;; We truncate the root directory so that the password list
;; resembles the output from `pass list`. To do so, we
;; truncate `~/.password-store/` in the pathname strings of
;; the passwords.
(format nil "~a/**/*.gpg" directory)))
(dir-length (length (namestring directory))))
(mapcar (lambda (x)
(subseq (namestring x) dir-length (- (length (namestring x)) (length ".gpg"))))
raw-list)))))
(defmethod clip-password ((password-interface password-store-interface) &key password-name service)
(declare (ignore service))
(execute password-interface (list "show" "--clip" password-name)
;; Outputting to string blocks `pass'.
:output 'nil))
(defvar *multiline-separator* ": *"
"A regular expression to separate keys from values in the `pass' multiline format.")
(defun parse-multiline (content)
"Return an alist of the multiple entries.
An entry is a sequence of
- a key string,
- a colon,
- optional spaces,
- a value string.
This is meant to handle the organization suggestion from
http://www.passwordstore.org/#organization.
Lines that don't match the format are ignored.
The first line (the password) is skipped."
(unless (uiop:emptyp content)
(let ((lines (str:split (string #\newline)
content)))
(delete nil (mapcar (lambda (line)
(let ((entry (ppcre:split *multiline-separator* line :limit 2)))
(when (= 2 (length entry))
entry)))
;; Skip first line to ignore password:
(rest lines))))))
(defvar *username-keys* '("login" "user" "username")
"A list of string keys used to find the `pass' username in `clip-username'.")
(defmethod clip-username ((password-interface password-store-interface) &key password-name service)
"Save the multiline entry that's prefixed with on of the `*username-keys*' to clipboard.
Case is ignored.
The prefix is discarded from the result and returned."
(declare (ignore service))
(when password-name
(let* ((content (execute password-interface (list "show" password-name)
:output '(:string :stripped t)))
(entries (parse-multiline content))
(username-entry (when entries
(some (lambda (key)
(find key entries :test #'string-equal :key #'first))
*username-keys*))))
(when username-entry
(trivial-clipboard:text (second username-entry))
(second username-entry)))))
(defmethod save-password ((password-interface password-store-interface)
&key password-name username password service)
(declare (ignore service))
(with-open-stream (st (make-string-input-stream (format nil "~a~%username:~a"
password
username)))
(execute password-interface (list "insert" "--multiline" password-name)
:input st))
(when (str:emptyp password)
(execute password-interface (list "generate" password-name))))
(defmethod password-correct-p ((password-interface password-store-interface))
t)
================================================
FILE: libraries/password-manager/password-security.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :password)
;;; Provide an interface to the command line "security" program used
;;; on BSD and Darwin systems to interface with the system keychain
(define-class security-interface (password-interface)
((executable (pathname->string (sera:resolve-executable "security"))))
(:export-class-name-p t)
(:export-accessor-names-p t))
(push 'security-interface *interfaces*)
(defmethod list-passwords ((password-interface security-interface))
(error "Listing passwords not supported by the 'security' interface."))
(defmethod clip-password ((password-interface security-interface) &key password-name service)
(clip-password-string password-interface
(str:replace-all
"\"" ""
(str:replace-first
"password: " ""
(nth-value 1
(execute password-interface
(list "find-internet-password"
"-a" password-name "-s" service "-g")
:error-output '(:string :stripped t)))))))
(defmethod clip-username ((password-interface security-interface) &key password-name service)
(declare (ignore password-name service))
(error "Username clipping is not supported by security interface."))
(defmethod password-correct-p ((password-interface security-interface)) t)
================================================
FILE: libraries/password-manager/password.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :password)
(define-class password-interface ()
((executable
nil
:type (or null string)
:documentation "The program to query for password information.")
(sleep-timer
15
:type alexandria:non-negative-real
:documentation "The amount of time to sleep, in seconds."))
(:export-class-name-p t)
(:export-accessor-names-p t))
(export-always 'list-passwords)
(defgeneric list-passwords (password-interface)
(:documentation "Retrieve all available passwords."))
(export-always 'clip-password)
(defgeneric clip-password (password-interface &key password-name service)
(:documentation "Retrieve specific password by name."))
(export-always 'clip-username)
(defgeneric clip-username (password-interface &key password-name service)
(:documentation "Retrieve specific login by name of the password entry."))
(export-always 'save-password)
(defgeneric save-password (password-interface
&key password-name username password service)
(:documentation "Save password to database.
If PASSWORD-NAME is empty, then generate a new password."))
(export-always 'password-correct-p)
(defgeneric password-correct-p (password-interface)
(:documentation "Return T if set password is correct, NIL otherwise."))
(export-always 'complete-interface)
(defgeneric complete-interface (password-interface)
(:method ((password-interface password-interface))
password-interface)
(:documentation "Return the PASSWORD-INTERFACE with all the misfilled fields corrected."))
(defgeneric execute (interface arguments &rest run-program-args &key wait-p &allow-other-keys)
(:method ((interface password-interface) (arguments list) &rest run-program-args &key (wait-p t) &allow-other-keys)
(apply (if wait-p #'uiop:run-program #'uiop:launch-program)
(append (uiop:ensure-list (executable interface)) arguments)
(alexandria:remove-from-plist run-program-args :wait-p)))
(:documentation "Execute the command matching the INTERFACE, with ARGS.
`uiop:run-program' is used underneath, with RUN-PROGRAM-ARGS being its
arguments.
When the WAIT-P is NIL, `uiop:launch-program' is used instead of
`uiop:run-program'."))
(defun safe-clipboard-text ()
"Return clipboard content, or \"\" if the content is not textual."
;; xclip errors out when the clipboard contains non-text:
;; https://github.com/astrand/xclip/issues/38#issuecomment-466625564.
(ignore-errors (trivial-clipboard:text)))
;;; Prerequisite Functions
(defmethod clip-password-string ((password-interface password-interface) pass)
(trivial-clipboard:text pass)
(bt:make-thread
(lambda ()
(sleep (sleep-timer password-interface))
(when (string= (safe-clipboard-text) pass)
;; Reset the clipboard so that the user does not accidentally paste
;; something else.
(trivial-clipboard:text "")))))
;;; Commands to wrap together.
(defun pathname->string (pathname)
"Like `namestring' but return NIL if PATHNAME is NIL."
(when pathname
(namestring pathname)))
(export-always '*interfaces*)
(defvar *interfaces* '())
================================================
FILE: libraries/text-buffer/package.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(uiop:define-package :text-buffer
(:use :cl)
(:export #:text-buffer #:cursor))
================================================
FILE: libraries/text-buffer/text-buffer.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :text-buffer)
(defclass text-buffer (cluffer-simple-line:line) ())
(defclass cursor (cluffer-simple-line::right-sticky-cursor)
((word-separation-characters
:accessor word-separation-characters
:initform '(":" "/" "." " " " "))))
(defmethod string-representation ((buffer text-buffer))
(with-output-to-string (out)
(map nil (lambda (string)
(write-string string out))
(cluffer:items buffer))))
(defmethod invisible-string-representation ((buffer text-buffer))
(make-string (cluffer:item-count buffer) :initial-element #\*))
(defmethod safe-forward ((cursor cursor))
(unless (cluffer:end-of-line-p cursor)
(cluffer:forward-item cursor)
(cluffer:item-before-cursor cursor)))
(defmethod safe-backward ((cursor cursor))
(unless (cluffer:beginning-of-line-p cursor)
(cluffer:backward-item cursor)
(cluffer:item-after-cursor cursor)))
(defmethod delete-item-forward ((cursor cursor))
(unless (cluffer:end-of-line-p cursor)
(cluffer:delete-item cursor)
t))
(defmethod delete-item-backward ((cursor cursor))
(unless (cluffer:beginning-of-line-p cursor)
(cluffer:erase-item cursor)
t))
(defmethod word-separation-chars-at-cursor-p ((cursor cursor) &key direction)
"Return non-nil when `word-separation-characters' are found
before/after the cursor position. You can specify to look before or
after the cursor by supplying :backward or :forward for the direction
value."
(find (cond ((and (not (cluffer:beginning-of-line-p cursor))
(eq direction :backward))
(cluffer:item-before-cursor cursor))
((and (not (cluffer:end-of-line-p cursor))
(eq direction :forward))
(cluffer:item-after-cursor cursor)))
(word-separation-characters cursor)
:test #'equal))
(defmethod move-to-word ((cursor cursor) &key direction conservative-word-move)
"Move the cursor to the boundary of a word and return its
position. A word is a string bounded by
`word-separation-characters'. Specify a `direction' of :forward or
:backward to change the movement."
(labels ((move-to-boundary (&key over-non-word-chars)
"Move the cursor while it finds
`word-separation-characters' adjacent to it. When
`over-non-word-chars' is `t' move the cursor otherwise."
(loop named movement
while (if over-non-word-chars
(word-separation-chars-at-cursor-p cursor :direction direction)
(not (word-separation-chars-at-cursor-p cursor :direction direction)))
unless (if (eq direction :backward)
(safe-backward cursor)
(safe-forward cursor))
do (return-from movement))))
(if (word-separation-chars-at-cursor-p cursor :direction direction)
(progn (move-to-boundary :over-non-word-chars t)
(when conservative-word-move (move-to-boundary)))
(move-to-boundary)))
(cluffer:cursor-position cursor))
(defmethod move-forward-word ((cursor cursor) &key conservative-word-move)
(move-to-word cursor :direction :forward
:conservative-word-move conservative-word-move))
(defmethod move-backward-word ((cursor cursor) &key conservative-word-move)
(move-to-word cursor :direction :backward
:conservative-word-move conservative-word-move))
(defmethod delete-word ((cursor cursor) &key direction)
"Delete characters until encountering the boundary of a
word. Specify a `direction' as :forward or :backward."
(let ((start-cursor-position (cluffer:cursor-position cursor))
(end-cursor-position
(if (eq direction :backward)
(move-backward-word cursor :conservative-word-move t)
(move-forward-word cursor :conservative-word-move t))))
(dotimes (i (abs (- start-cursor-position end-cursor-position)))
(if (eq direction :backward)
(cluffer:delete-item cursor)
(cluffer:erase-item cursor)))))
(defmethod delete-forward-word ((cursor cursor))
"Delete characters forward until encountering the end of a word."
(delete-word cursor :direction :forward))
(defmethod delete-backward-word ((cursor cursor))
"Delete characters backward until encountering the end of a word."
(delete-word cursor :direction :backward))
(defmethod kill-forward-line ((cursor cursor))
(loop while (delete-item-forward cursor)))
(defmethod insert-string ((cursor cursor) string)
(loop for char across string do
(cluffer:insert-item cursor (string char))))
(defmethod word-at-cursor ((cursor cursor))
"Return word at cursor. If cursor is between two words, return the
first one."
(let ((original-cursor-position (cluffer:cursor-position cursor)))
(move-backward-word cursor)
(let* ((delta (abs (- (cluffer:cursor-position cursor)
(move-forward-word cursor))))
(word-at-cursor (reverse (apply #'concatenate 'string
(loop repeat delta
collect (safe-backward cursor))))))
(setf (cluffer:cursor-position cursor) original-cursor-position)
word-at-cursor)))
(defmethod replace-word-at-cursor ((cursor cursor) string)
(unless (uiop:emptyp (word-at-cursor cursor))
(move-backward-word cursor)
(delete-forward-word cursor))
(insert-string cursor string))
(defmethod kill-line ((cursor cursor))
"Kill the complete line."
(cluffer:beginning-of-line cursor)
(kill-forward-line cursor))
(defun word-start (s position &optional (white-spaces '(#\space #\no-break_space)))
"Return the index of the beginning word at POSITION in string S."
(apply #'max
(mapcar (lambda (char)
(let ((pos (position char s
:end position
:from-end t)))
(if pos
(1+ pos)
0)))
white-spaces)))
(defun word-end (s position &optional (white-spaces '(#\space #\no-break_space)))
"Return the index of the end of the word at POSITION in string S."
(apply #'min
(mapcar (lambda (char)
(or (position char s :start position)
(length s)))
white-spaces)))
================================================
FILE: libraries/theme/README.org
================================================
#+TITLE: Theme library for Nyxt
#+PROPERTY: :results silent
* Overview
This general purpose theme library provides the means to customize the colors
and fonts of Nyxt's UI. Besides exposing the set of tweakable options,
opinionated defaults are provided.
Owing to its flexibility, it can be used to theme other projects.
** Palette's rationale
The following semantic color groups are defined:
- ~background~ :: large surfaces.
- ~primary~ :: primary interface elements.
- ~secondary~ :: secondary or decorative interface elements.
- ~action~ :: focus or call to action.
- ~success~ :: successful completion, download, or evaluation.
- ~warning~ :: errors, invalid operations, or consequential actions.
- ~highlight~ :: eye-catching text highlighting.
For each group, 2 variation colors with more and less contrast are defined.
These are intended for cases of complex and overlapping
interfaces. E.g. ~background+~ and ~background-~.
Additionally, a foreground color is defined. E.g. ~on-background~.
This rationale is loosely based on [[https://m2.material.io/design/material-theming/implementing-your-theme.html][Google Material Design Guidelines]].
** Example
#+begin_src lisp
(defvar my-theme
(make-instance 'theme:theme
:background-color "#F0F0F0"
:primary-color "#595959"
:secondary-color "#E6E6E6"
:action-color "#5FCFFF"
:highlight-color "#FAC090"
:success-color "#AEE5BE"
:warning-color "#F3B5AF"
:font-family "Iosevka"
:monospace-font-family "Iosevka")
"Example theme.
When the values for on-colors are omitted, they're automatically set to either
black or white, according to what achieves a better contrast.
When the values for color+ and color- are omitted, they fallback on regular
color values.
Note that not all semantic color groups need to be defined.")
;; Set the theme in Nyxt's config file
(define-configuration browser ((theme my-theme)))
#+end_src
* Defaults
We suggest following the WCAG (Web Content Accessibility Guidelines) with
respect to contrast ratios. The lowest standard (Level AA) requires a ratio of
4.5:1, while a higher standard (Level AAA) requires 7:1.
The target contrast ratios for the default palette are summarized below.
- Minus colors (e.g. ~background-~) :: >= 4.5:1
- Regular colors (e.g. ~background~) :: >= 6.5:1
- Plus colors (e.g. ~background+~) :: >= 8.5:1
** Light theme
| Color Name | Value | ~on-*~ Value | Contrast |
|---------------+---------+--------------+----------|
| ~background+~ | #FFFFFF | #000000 | 21.00 |
| ~background~ | #F8F8F8 | #000000 | 19.77 |
| ~background-~ | #ECECEC | #000000 | 17.78 |
|---------------+---------+--------------+----------|
| ~primary+~ | #474747 | #FFFFFF | 9.29 |
| ~primary~ | #555555 | #FFFFFF | 7.46 |
| ~primary-~ | #686868 | #FFFFFF | 5.57 |
|---------------+---------+--------------+----------|
| ~secondary+~ | #BFBFBF | #000000 | 11.42 |
| ~secondary~ | #A6A6A6 | #000000 | 8.63 |
| ~secondary-~ | #909090 | #000000 | 6.58 |
|---------------+---------+--------------+----------|
| ~action+~ | #72CDFE | #000000 | 11.88 |
| ~action~ | #37A8E4 | #000000 | 7.88 |
| ~action-~ | #178DCC | #000000 | 5.72 |
|---------------+---------+--------------+----------|
| ~highlight+~ | #FFFA66 | #000000 | 19.12 |
| ~highlight~ | #FCE304 | #000000 | 16.13 |
| ~highlight-~ | #FCBA04 | #000000 | 12.16 |
|---------------+---------+--------------+----------|
| ~success+~ | #71FE7D | #000000 | 16.18 |
| ~success~ | #8AEA92 | #000000 | 14.26 |
| ~success-~ | #86D58E | #000000 | 11.92 |
|---------------+---------+--------------+----------|
| ~warning+~ | #88040D | #FFFFFF | 10.14 |
| ~warning~ | #AF1923 | #FFFFFF | 7.03 |
| ~warning-~ | #D2232E | #FFFFFF | 5.22 |
|---------------+---------+--------------+----------|
#+TBLFM: $4='(contrast $2 $3);%.2f
** Dark theme
| Color Name | Value | ~on-*~ Value | Contrast |
|---------------+---------+--------------+----------|
| ~background+~ | #000000 | #FFFFFF | 21.00 |
| ~background~ | #121212 | #FFFFFF | 18.73 |
| ~background-~ | #333333 | #FFFFFF | 12.63 |
|---------------+---------+--------------+----------|
| ~primary+~ | #EFA671 | #000000 | 10.36 |
| ~primary~ | #E48D4E | #000000 | 8.22 |
| ~primary-~ | #D7752F | #000000 | 6.47 |
|---------------+---------+--------------+----------|
| ~secondary+~ | #683008 | #FFFFFF | 10.42 |
| ~secondary~ | #844115 | #FFFFFF | 7.64 |
| ~secondary-~ | #9F592D | #FFFFFF | 5.33 |
|---------------+---------+--------------+----------|
| ~action+~ | #481FA2 | #FFFFFF | 10.54 |
| ~action~ | #571FD2 | #FFFFFF | 8.29 |
| ~action-~ | #763DF2 | #FFFFFF | 5.65 |
|---------------+---------+--------------+----------|
| ~highlight+~ | #FC83F2 | #000000 | 9.67 |
| ~highlight~ | #F46DE8 | #000000 | 8.20 |
| ~highlight-~ | #EA43DD | #000000 | 6.35 |
|---------------+---------+--------------+----------|
| ~success+~ | #87FCDF | #000000 | 17.02 |
| ~success~ | #4CFBCF | #000000 | 16.01 |
| ~success-~ | #05F4CD | #000000 | 14.83 |
|---------------+---------+--------------+----------|
| ~warning+~ | #FFD152 | #000000 | 14.49 |
| ~warning~ | #FCBA04 | #000000 | 12.16 |
| ~warning-~ | #FCA904 | #000000 | 10.82 |
|---------------+---------+--------------+----------|
#+TBLFM: $4='(contrast $2 $3);%.2f
** Remarks
The minus and plus colors, when omitted, are set to the corresponding regular
color.
~on-colors~, when omitted, are set to either black or white, depending on which
results in a higher contrast ratio with its corresponding ~color~.
One might be tempted to think that ~on-colors~ are meant to be used solely for
text, but the principle holds more generality, when placing tiny elements over
huge surfaces.
Take blue and yellow, colors that have a poor contrast ratio. Consider that,
(1) you inscribe a blue circle that covers most of the yellow square's surface,
and (2) you were to draw a tiny blue cross on the same yellow background. In
situation (1), you still properly discern the circle, whereas in (2) you'd
struggle to see it.
* COMMENT TBLFM Code
Auxiliary code to update contrast ratios on the tables shown in this document.
Instructions:
- Evaluate the cell below;
- Run command =org-table-recalculate-buffer-tables=.
#+begin_src emacs-lisp
(defun contrast (c1 c2)
"Measure WCAG contrast ratio between C1 and C2.
C1 and C2 are color values written in hexadecimal RGB."
(cl-flet ((wcag-formula (hex)
(cl-loop for k in '(0.2126 0.7152 0.0722)
for x in (color-name-to-rgb hex)
sum (* k (if (<= x 0.03928)
(/ x 12.92)
(expt (/ (+ x 0.055) 1.055) 2.4))))))
(let ((ct (/ (+ (wcag-formula c1) 0.05)
(+ (wcag-formula c2) 0.05))))
(max ct (/ ct)))))
#+end_src
================================================
FILE: libraries/theme/package.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(uiop:define-package :theme
(:use :cl)
(:shadow #:warning)
(:import-from :serapeum #:export-always)
(:import-from :nclasses #:define-class))
================================================
FILE: libraries/theme/tests/tests.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package cl-user)
(uiop:define-package :theme/tests
(:use :cl :lisp-unit2)
(:import-from :theme))
(in-package :theme/tests)
(defvar *theme* (make-instance 'theme:theme
:background-color "white"
:action-color "#37A8E4")
"Dummy theme for testing.")
(define-test fallback-colors ()
(assert-string= "white" (theme:background-color- *theme*))
(assert-string= "white" (theme:background-color+ *theme*))
(assert-string= "black" (theme:on-background-color *theme*))
(assert-false (theme:primary-color+ *theme*))
(assert-false (theme:primary-color *theme*))
(assert-false (theme:primary-color- *theme*))
(assert-false (theme:on-primary-color *theme*)))
(define-test css-substitution ()
(assert-string= "a{background-color:white;color:black;}h1{color:#37A8E4 !important;}"
(let ((lass:*pretty* nil))
(theme:themed-css *theme*
`(a
:background-color ,theme:background-color
:color ,theme:on-background-color)
`(h1
:color ,theme:action-color "!important")))))
(defmethod assert-contrast ((theme theme:theme)
&key (min-color+-contrast 8.5)
(min-color-contrast 6.5)
(min-color--contrast 4.5))
(macrolet ((assert-contrast-ratio (color1 color2 min-contrast)
`(assert-true (>= (theme:contrast-ratio ,color1 ,color2)
,min-contrast))))
(multiple-value-bind (on-colors regular-colors minus-colors plus-colors)
(values-list
(theme:filter-palette (list (alexandria:curry #'uiop:string-prefix-p "ON-")
(alexandria:rcurry #'uiop:string-suffix-p "COLOR")
(alexandria:rcurry #'uiop:string-suffix-p "COLOR-")
(alexandria:rcurry #'uiop:string-suffix-p "COLOR+"))
(theme:palette theme)))
(loop for on-color in on-colors
for regular-color in regular-colors
for minus-color in minus-colors
for plus-color in plus-colors
do (assert-contrast-ratio (funcall regular-color theme)
(funcall on-color theme)
min-color-contrast)
do (assert-contrast-ratio (funcall plus-color theme)
(funcall on-color theme)
min-color+-contrast)
do (assert-contrast-ratio (funcall minus-color theme)
(funcall on-color theme)
min-color--contrast)))))
(define-test default-light-theme-contrast ()
(assert-contrast theme:+light-theme+))
(define-test default-dark-theme-contrast ()
(assert-contrast theme:+dark-theme+))
================================================
FILE: libraries/theme/theme.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :theme)
(define-class theme ()
((background-color+
:documentation "More contrasting variation of `background-color'.")
(background-color
:documentation "The background color of the theme.")
(background-color-
:documentation "Less contrasting variation of `background-color'.")
(on-background-color
:documentation "The color for elements/text in front of `background-color'.")
(primary-color+
:documentation "More contrasting variation of `primary-color'.")
(primary-color
:documentation "Primary UI element color.")
(primary-color-
:documentation "Less contrasting variation of `primary-color'.")
(on-primary-color
:documentation "The color for elements/text in front of `primary-color'.")
(secondary-color+
:documentation "More contrasting variation of `secondary-color'.")
(secondary-color
:documentation "Secondary UI element color.")
(secondary-color-
:documentation "Less contrasting variation of `secondary-color'.")
(on-secondary-color
:documentation "The color for elements/text in front of `secondary-color'.")
(action-color+
:documentation "More contrasting variation of `action-color'.")
(action-color
:documentation "Color for focused and important elements.")
(action-color-
:documentation "Less contrasting variation of `action-color'.")
(on-action-color
:documentation "The color for elements/text in front of `action-color'.")
(highlight-color+
:documentation "More contrasting variation of `highlight-color'.")
(highlight-color
:documentation "The color for elements requiring attention.")
(highlight-color-
:documentation "Less contrasting variation of `highlight-color'.")
(on-highlight-color
:documentation "The color for elements/text in front of `highlight-color'.")
(success-color+
:documentation "More contrasting variation of `success-color'.")
(success-color
:documentation "The color to express success.")
(success-color-
:documentation "Less contrasting variation of `success-color'.")
(on-success-color
:documentation "The color for elements/text in front of `success-color'.")
(warning-color+
:documentation "More contrasting variation of `warning-color'.")
(warning-color
:documentation "The color to express errors.")
(warning-color-
:documentation "Less contrasting variation of `warning-color'.")
(on-warning-color
:documentation "The color for elements/text in front of `warning-color'.")
(font-family
"Public Sans"
:documentation "The font family to use by default.")
(monospace-font-family
"DejaVu Sans Mono"
:documentation "The monospace font family to use by default."))
(:export-class-name-p t)
(:export-accessor-names-p t)
(:export-predicate-name-p t))
(defmethod initialize-instance :after ((theme theme) &key)
(multiple-value-bind (on-colors regular-colors minus-colors plus-colors)
(values-list
(filter-palette (list (alexandria:curry #'uiop:string-prefix-p "ON-")
(alexandria:rcurry #'uiop:string-suffix-p "COLOR")
(alexandria:rcurry #'uiop:string-suffix-p "COLOR-")
(alexandria:rcurry #'uiop:string-suffix-p "COLOR+"))
(palette theme)))
(loop for on-color in on-colors
for regular-color in regular-colors
for minus-color in minus-colors
for plus-color in plus-colors
do (when (and (not (slot-value theme on-color))
(slot-value theme regular-color))
(setf (slot-value theme on-color)
(contrasting-color (slot-value theme regular-color))))
do (when (and (not (slot-value theme minus-color))
(slot-value theme regular-color))
(setf (slot-value theme minus-color)
(slot-value theme regular-color)))
do (when (and (not (slot-value theme plus-color))
(slot-value theme regular-color))
(setf (slot-value theme plus-color)
(slot-value theme regular-color))))))
(export-always 'dark-p)
(defmethod dark-p ((theme theme))
"Whether the theme is dark."
(when (string= "white" (contrasting-color (background-color theme))) t))
(export-always 'palette)
(defmethod palette ((theme theme))
"Return color slots of THEME.
Example that returns the palette's color values:
(mapcar (alexandria:rcurry #'funcall +light-theme+)
(palette +light-theme+))"
(serapeum:filter (alexandria:curry #'serapeum:string-contains-p "COLOR")
(mopu:direct-slot-names theme)
:key #'string))
(export-always 'filter-palette)
(defun filter-palette (preds palette)
"Partition PALETTE according to PREDS."
(serapeum:partitions preds palette :key #'string))
(export-always 'with-theme)
(defmacro with-theme (theme-instance &body body)
"Evaluate BODY with THEME and THEME's slots let-bound."
`(let ((theme ,theme-instance))
(with-slots ,(mopu:direct-slot-names 'theme) theme
,@body)))
(export-always 'themed-css)
(defmacro themed-css (theme &body forms)
"Generate CSS via lass FORMS styled according to THEME.
Example:
(themed-css (make-instance 'theme :background-color \"white\")
`(|h1,h2,h3,h4,h5,h6|
:border-style \"solid\"
:border-color ,theme:on-background-color)
`(p
:color ,(if (theme:dark-p theme:theme) \"yellow\" \"green\")))"
`(with-theme ,theme (lass:compile-and-write ,@forms)))
(export-always '+light-theme+)
(defvar +light-theme+
(make-instance 'theme
:background-color+ "#FFFFFF"
:background-color "#F8F8F8"
:background-color- "#ECECEC"
:primary-color+ "#999999"
:primary-color "#686868"
:primary-color- "#555555"
:secondary-color+ "#BFBFBF"
:secondary-color "#A6A6A6"
:secondary-color- "#909090"
:action-color+ "#72CDFE"
:action-color "#37A8E4"
:action-color- "#178DCC"
:highlight-color+ "#FFFA66"
:highlight-color "#FCE304"
:highlight-color- "#FCBA04"
:success-color+ "#71FE7D"
:success-color "#8AEA92"
:success-color- "#86D58E"
:warning-color+ "#88040D"
:warning-color "#AF1923"
:warning-color- "#D2232E"))
(export-always '+dark-theme+)
(defvar +dark-theme+
(make-instance 'theme:theme
:background-color- "#3B4252"
:background-color "#2E3440"
:background-color+ "#434C5E"
:on-background-color "#E5E9F0"
:primary-color- "#5E81AC"
:primary-color "#5E81AC"
:primary-color+ "#81A1C1"
:on-primary-color "#ECEFF4"
:secondary-color- "#4C566A"
:secondary-color "#4C566A"
:secondary-color+ "#5E81AC"
:on-secondary-color "#E5E9F0"
:action-color- "#88C0D0"
:action-color "#88C0D0"
:action-color+ "#81A1C1"
:on-action-color "#2E3440"
:success-color- "#8FBCBB"
:success-color "#8FBCBB"
:success-color+ "#81A1C1"
:on-success-color "#2E3440"
:highlight-color- "#B48EAD"
:highlight-color "#B48EAD"
:highlight-color+ "#D8DEE9"
:on-highlight-color "#2E3440"
:warning-color- "#EBCB8B"
:warning-color "#EBCB8B"
:warning-color+ "#D08770"
:on-warning-color "#2E3440"))
================================================
FILE: libraries/theme/utilities.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :theme)
(serapeum:-> relative-luminance ((or string integer cl-colors-ng:rgb cl-colors-ng:hsv))
real)
(defun relative-luminance (color)
"Compute relative luminance of COLOR."
;; See https://www.w3.org/WAI/GL/wiki/Relative_luminance
(loop for const in '(0.2126 0.7152 0.0722)
for rgb-component in (list (cl-colors-ng:rgb-red (cl-colors-ng:as-rgb color))
(cl-colors-ng:rgb-green (cl-colors-ng:as-rgb color))
(cl-colors-ng:rgb-blue (cl-colors-ng:as-rgb color)))
sum (* const (if (<= rgb-component 0.04045)
(/ rgb-component 12.92)
(expt (/ (+ rgb-component 0.055) 1.055) 2.4)))))
(serapeum:-> contrast-ratio ((or string integer cl-colors-ng:rgb cl-colors-ng:hsv)
(or string integer cl-colors-ng:rgb cl-colors-ng:hsv))
(real 0 21)) ; Ratio between black and white.
(export-always 'contrast-ratio)
(defun contrast-ratio (color1 color2)
"Compute contrast ratio between COLOR1 and COLOR2."
;; See https://www.w3.org/WAI/GL/wiki/Contrast_ratio
(let ((ratio (/ (+ (relative-luminance color1) 0.05)
(+ (relative-luminance color2) 0.05))))
(max ratio (/ ratio))))
(serapeum:-> contrasting-color ((or string integer cl-colors-ng:rgb cl-colors-ng:hsv)) string)
(export-always 'contrasting-color)
(defun contrasting-color (color)
"Determine whether black or white best contrasts with COLOR."
(if (>= (contrast-ratio color "white")
(contrast-ratio color "black"))
"white"
"black"))
================================================
FILE: libraries/user-interface/package.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(uiop:define-package :user-interface
(:use :cl))
================================================
FILE: libraries/user-interface/user-interface.lisp
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :user-interface)
;; Taken from serapeum
(defmacro export-always (symbols &optional (package nil package-supplied?))
"Like `export', but also evaluated at compile time."
`(eval-when (:compile-toplevel :load-toplevel :execute)
(export ,symbols ,@(and package-supplied? (list package)))))
(export-always 'id)
(defvar *id* 0 "Counter used to generate a unique ID.")
(defun unique-id ()
(format nil "ui-element-~d" (incf *id*)))
(defgeneric to-html (object)
(:documentation "The HTML representation of OBJECT.
A form suitable to be compiled by Spinneret."))
(export-always 'buffer)
(defclass ui-element ()
((id :accessor id)
(buffer :accessor buffer :initarg :buffer
:documentation "Buffer where element is drawn.")))
(defmethod initialize-instance :after ((element ui-element) &key)
(setf (id element) (unique-id)))
(export-always 'connect)
(defmethod connect ((element ui-element) buffer)
(setf (buffer element) buffer))
(export-always 'update)
(defgeneric update (ui-element)
(:documentation "Propagate changes to the buffer."))
(export-always 'button)
(export-always 'text)
(export-always 'action)
(defclass button (ui-element)
((text :initform "" :initarg :text :accessor text)
(alt-text :initform "" :initarg :alt-text :accessor alt-text)
(action :initform "" :initarg :action :accessor action)))
(defmethod (setf text) :after (text (button button))
(declare (ignorable text))
(when (slot-boundp button 'buffer)
(update button)))
(defmethod (setf action) :after (action (button button))
(declare (ignorable action))
(when (slot-boundp button 'buffer)
(update button)))
(defmethod (setf alt-text) :after (text (button button))
(declare (ignorable text))
(when (slot-boundp button 'buffer)
(update button)))
(export-always 'to-html)
(defmethod to-html ((button button))
(spinneret:with-html
(:button :id (id button)
:class "button"
:title (alt-text button)
:onclick (action button)
(text button))))
(export-always 'paragraph)
(defclass paragraph (ui-element)
((text :initform "" :initarg :text :accessor text)))
(defmethod (setf text) :after (text (paragraph paragraph))
(declare (ignorable text))
(when (slot-boundp paragraph 'buffer)
(update paragraph)))
(defmethod to-html ((paragraph paragraph))
(spinneret:with-html
(:p :id (id paragraph) (text paragraph))))
(export-always 'progress-bar)
(export-always 'percentage)
(defclass progress-bar (ui-element)
((percentage :initform 0
:initarg :percentage
:accessor percentage
:documentation "The percentage the progress bar is
filled up, use a number between 0 and 100.")))
(defmethod to-html ((progress-bar progress-bar))
(spinneret:with-html
(:div :class "progress-bar-base"
(:div :class "progress-bar-fill"
:id (id progress-bar)
;; empty string to force markup to make closing :div tag
""))))
(defmethod (setf percentage) :after (percentage (progress-bar progress-bar))
(declare (ignorable percentage))
(when (slot-boundp progress-bar 'buffer)
(update progress-bar)))
================================================
FILE: licenses/ASSET-LICENSE
================================================
All non-source code assets are licensed as CC BY-SA.
Creative Commons Deed
This is a human-readable summary of the full license below.
You are free:
to Share— to copy, distribute and transmit the work, and
to Remix— to adapt the work for any purpose, even commercially.
Under the following conditions:
Attribution— You must attribute the work in the manner specified by the
author or licensor (but not in any way that suggests that they endorse
you or your use of the work.) Share Alike—If you alter, transform, or
build upon this work, you may distribute the resulting work only under
the same, similar or a compatible license. With the understanding
that:
Waiver— Any of the above conditions can be waived if you get
permission from the copyright holder. Other Rights—In no way are any
of the following rights affected by the license: your fair dealing or
fair use rights; the author's moral rights; and rights other persons
may have either in the work itself or in how the work is used, such as
publicity or privacy rights.
Notice—For any reuse or distribution, you must make clear to others
the license terms of this work. The best way to do that is with a link
to https://creativecommons.org/licenses/by-sa/3.0/
================================================
FILE: licenses/DejaVu Fonts License.txt
================================================
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Arev Fonts Copyright
------------------------------
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and
associated documentation files (the "Font Software"), to reproduce
and distribute the modifications to the Bitstream Vera Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not
be used in advertising or otherwise to promote the sale, use or other
dealings in this Font Software without prior written authorization
from Tavmjong Bah. For further information, contact: tavmjong @ free
. fr.
================================================
FILE: licenses/SOURCE-LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2017-2025, Atlas Engineer LLC.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: makefile
================================================
# SPDX-FileCopyrightText: Atlas Engineer LLC
# SPDX-License-Identifier: BSD-3-Clause
## Use Bourne shell syntax.
SHELL = /bin/sh
UNAME := $(shell uname)
LISP ?= sbcl
SBCL_FLAGS =
ifeq ($(LISP), sbcl)
SBCL_FLAGS=--dynamic-space-size $(shell sbcl --noinform --no-userinit --non-interactive --eval '(prin1 (max 3072 (/ (sb-ext:dynamic-space-size) 1024 1024)))' --quit | tail -1)
endif
LISP_FLAGS ?= $(SBCL_FLAGS) --no-userinit --non-interactive
NYXT_SUBMODULES ?= true
NYXT_RENDERER ?= electron
NASDF_USE_LOGICAL_PATHS ?= true
NODE_SETUP ?= true
export NYXT_SUBMODULES
export NYXT_RENDERER
export NASDF_USE_LOGICAL_PATHS
export NODE_SETUP
.PHONY: help
help:
@cat INSTALL
makefile_dir := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
ifeq ($(NYXT_SUBMODULES),true)
CL_SOURCE_REGISTRY = $(makefile_dir)_build//
export CL_SOURCE_REGISTRY
endif
lisp_eval:=$(LISP) $(LISP_FLAGS) \
--eval '(require "asdf")' \
--eval '(asdf:load-asd "$(makefile_dir)/libraries/nasdf/nasdf.asd")' \
--eval '(asdf:load-asd "$(makefile_dir)/nyxt.asd")' \
--eval
lisp_quit:=--eval '(uiop:quit 0 \#+bsd nil)'
## asdf:load-system is a bit slow on :nyxt/$(NYXT_RENDERER)-application, so we
## keep a Make dependency on the Lisp files.
lisp_files := nyxt.asd $(shell find . -type f -name '*.lisp')
nyxt: $(lisp_files)
if [ "$(NYXT_RENDERER)" = "electron" ] && \
[ "$(NODE_SETUP)" = "true" ] && \
[ "$(NYXT_SUBMODULES)" = "true" ]; then \
$(MAKE) -C $(makefile_dir)_build/cl-electron install; \
fi
$(lisp_eval) '(asdf:load-system :nyxt/$(NYXT_RENDERER)-application)' \
--eval '(asdf:make :nyxt/$(NYXT_RENDERER)-application)' \
$(lisp_quit) || (printf "\n%s\n%s\n" "Compilation failed, see the above stacktrace." && exit 1)
.PHONY: all
all: nyxt
.PHONY: doc
doc:
$(lisp_eval) '(asdf:load-system :nyxt)' \
--eval '(asdf:load-system :nyxt/documentation)' $(lisp_quit)
.PHONY: check
check:
$(lisp_eval) '(asdf:test-system :nyxt)'
.PHONY: clean
clean:
rm nyxt
================================================
FILE: nyxt.asd
================================================
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
#-asdf3.1 (error "Nyxt requires ASDF 3.1.2")
;; WARNING: We _must_ declare the translation host or else ASDF won't recognize
;; the pathnames as logical-pathnames, thus returning the system directory
;; instead.
(setf (logical-pathname-translations "NYXT") nil)
(defsystem "nyxt"
:defsystem-depends-on ("nasdf")
:class :nasdf-system
:version "4" ; 4.0.0-pre-release-3
:author "Atlas Engineer LLC"
:homepage "https://nyxt-browser.com"
:description "Extensible web browser in Common Lisp"
:license "BSD 3-Clause"
:depends-on (alexandria
bordeaux-threads
calispel
cl-base64
cl-colors-ng
cl-gopher
cl-json
cl-ppcre
cl-ppcre-unicode
cl-prevalence
cl-qrencode
cl-tld
closer-mop
clss
dexador
enchant
flexi-streams
iolib
iolib/os
lass
local-time
log4cl
lparallel
nclasses
nfiles
nhooks
njson/cl-json
nkeymaps
nsymbols/star
parenscript
phos
plump
prompter
py-configparser
quri
serapeum
spinneret
sqlite
str
trivia
trivial-arguments
trivial-clipboard
trivial-package-local-nicknames
trivial-types
unix-opts
;; Local systems:
nyxt/analysis
nyxt/download-manager
nyxt/password-manager
nyxt/text-buffer
nyxt/theme
nyxt/user-interface)
:pathname #p"NYXT:source;"
:components ((:file "utilities")
(:file "types")
(:file "package" :depends-on ("utilities" "types"))
(:module "Utilities"
:pathname ""
:depends-on ("package")
:components
((:file "time")
(:file "keyscheme")
(:file "conditions")
(:file "user-interface")))
(:module "Core"
:pathname ""
:depends-on ("Utilities")
:serial t
:components
((:file "renderer")
(:file "global")
(:file "concurrency")
(:file "user-files")
(:file "user-classes")
(:file "configuration")
(:file "parenscript-macro")
(:file "message")
(:file "command")
(:file "renderer-script")
(:file "urls")
(:file "inspector")
(:file "dom")
(:file "search-engine")
(:file "buffer")
(:file "window")
(:file "mode")
(:file "history")
(:file "spinneret-tags")
(:file "browser")
(:file "foreign-interface")
(:file "clipboard")
(:file "color")
(:file "input")
(:file "prompt-buffer")
(:file "command-commands")
(:file "recent-buffers")
(:file "external-editor")))
(:module "Core modes"
:pathname "mode"
:depends-on ("Core")
:components
((:file "input-edit")
(:file "buffer-listing")
(:file "message")
(:file "passthrough")
(:file "document" :depends-on ("passthrough"))
(:file "hint" :depends-on ("document"))
(:file "search-buffer")
(:file "spell-check" :depends-on ("document"))
(:file "help" :depends-on ("document" "search-buffer"))
(:file "history")
(:file "keyscheme")
(:file "process")))
(:file "describe" :depends-on ("Core modes"))
(:module "Prompter modes"
:pathname "mode"
:depends-on ("describe" "Core modes")
:components
((:file "prompt-buffer")
(:file "hint-prompt-buffer" :depends-on ("prompt-buffer"))
(:file "file-manager" :depends-on ("prompt-buffer"))
(:file "download" :depends-on ("file-manager"))))
(:file "mode/base" :depends-on ("Core modes"))
(:file "status" :depends-on ("Core"))
(:module "Help"
:pathname ""
:depends-on ("Core modes" "Modes")
:components
((:file "help")
(:file "about")
(:file "tutorial")))
(:file "configuration-commands" :depends-on ("Help"))
(:file "start" :depends-on ("configuration-commands"))
(:file "manual" :depends-on ("configuration-commands"))
(:module "Modes"
:pathname "mode"
:depends-on ("Core modes")
:components
((:file "annotate")
(:file "autofill")
(:file "bookmark")
(:file "bookmarklets")
(:file "cruise-control" :depends-on ("repeat"))
(:file "emacs")
(:file "expedition")
(:file "history-migration")
(:file "macro-edit")
(:file "no-sound")
(:file "password")
(:file "reading-line")
(:file "repeat")
(:file "small-web")
(:file "style" :depends-on ("bookmarklets"))
(:file "visual")
(:file "vi")
(:file "watch"))))
:in-order-to ((test-op (test-op "nyxt/tests")
;; Dumping the manual may catch errors.
(compile-op "nyxt/documentation")
;; Subsystems:
(test-op "nyxt/analysis")
(test-op "nyxt/theme"))))
(defsystem "nyxt/tests"
:defsystem-depends-on ("nasdf")
:class :nasdf-test-system
:depends-on (nyxt lisp-unit2)
:pathname #p"NYXT:tests;"
:components ((:file "package")
(:file "define-configuration")
(:file "prompt-buffer")
(:file "urls")
(:file "user-script-parsing")
(:file "mode")
(:module "Modes"
:pathname "mode"
:components
((:file "autofill")
(:file "annotate")
(:file "base")
(:file "blocker")
(:file "bookmark")
(:file "bookmarklets")
(:file "buffer-listing")
(:file "certificate-exception")
(:file "cruise-control")
(:file "document")
(:file "download")
(:file "emacs")
(:file "expedition")
(:file "file-manager")
(:file "force-https")
(:file "help")
(:file "hint-prompt-buffer")
(:file "hint")
(:file "history")
(:file "input-edit")
(:file "keyscheme")
(:file "macro-edit")
(:file "message")
(:file "no-image")
(:file "no-script")
(:file "no-sound")
(:file "no-webgl")
(:file "passthrough")
(:file "password")
(:file "process")
(:file "prompt-buffer")
(:file "proxy")
(:file "reading-line")
;; TODO Fix repeat-mode architecture. Visit the file below for
;; more information.
;; (:file "repeat")
(:file "search-buffer")
(:file "small-web")
(:file "spell-check")
(:file "style")
(:file "vi")
;; TODO Fix visual-mode architecture. Visit the file below for
;; more information.
;; (:file "visual")
(:file "user-script")
(:file "watch"))))
:test-suite-args (:package :nyxt/tests))
(defsystem "nyxt/benchmarks"
:defsystem-depends-on ("nasdf")
:class :nasdf-system
:depends-on (nyxt alexandria trivial-benchmark)
:pathname #p"NYXT:tests;benchmarks;"
:components ((:file "package")
(:file "prompter"))
:perform (test-op (op c)
(eval-input
"(nasdf:print-benchmark
(alexandria:hash-table-alist
(benchmark:run-package-benchmarks :package :nyxt/benchmarks
:verbose t)))")))
(defsystem "nyxt/documentation"
:depends-on (nyxt)
:perform (compile-op (o c)
(with-open-file (out "manual.html" :direction :output :if-exists :supersede)
(write-string (symbol-call :nyxt :manual-html) out)
(format *error-output* "Manual dumped to ~s.~&" "manual.html"))))
(defsystem "nyxt/gtk"
:defsystem-depends-on ("nasdf")
:class :nasdf-system
:depends-on (nyxt cl-webkit2)
:pathname #p"NYXT:source;"
:components ((:file "renderer/gtk")
;; TODO: Port to other renderers.
(:file "mode/blocker")
(:file "mode/certificate-exception")
(:file "mode/force-https")
(:file "mode/user-script")
(:file "mode/no-image")
(:file "mode/no-script")
(:file "mode/no-webgl")
(:file "mode/proxy")))
(defsystem "nyxt/gi-gtk"
:defsystem-depends-on ("nasdf")
:class :nasdf-system
:depends-on (nyxt/gtk cl-gobject-introspection)
:pathname #p"NYXT:source;renderer;"
:components ((:file "gi-gtk"))
:in-order-to ((test-op (test-op "nyxt/gi-gtk/tests")
(test-op "nyxt/tests")
;; Dumping the manual may catch errors.
(compile-op "nyxt/documentation")
;; Subsystems:
(test-op "nyxt/analysis")
(test-op "nyxt/theme"))))
(defsystem "nyxt/gi-gtk/tests"
:defsystem-depends-on ("nasdf")
:class :nasdf-test-system
:depends-on (nyxt/gi-gtk lisp-unit2)
:pathname #p"NYXT:tests;renderer;"
:serial t
:components ((:file "package")
(:file "set-url")
(:file "custom-schemes")
(:file "search-buffer"))
:test-suite-args (:package :nyxt/tests/renderer))
(defsystem "nyxt/electron"
:depends-on (nyxt cl-electron)
:pathname #p"NYXT:source;renderer;"
:components ((:file "electron")))
;; We should not set the
gitextract_qdmfzfut/
├── .dir-locals.el
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── ui_request.md
│ ├── SECURITY.md
│ └── pull_request_template.md
├── .gitignore
├── .gitmodules
├── INSTALL
├── README.org
├── _build/
│ └── README.org
├── assets/
│ ├── Info.plist
│ ├── nyxt.appimage.desktop
│ ├── nyxt.desktop
│ ├── nyxt.icns
│ └── nyxt.metainfo.xml
├── developer-manual.org
├── libraries/
│ ├── analysis/
│ │ ├── README.org
│ │ ├── analysis.lisp
│ │ ├── composite-sequence.lisp
│ │ ├── data.lisp
│ │ ├── dbscan.lisp
│ │ ├── document-vector.lisp
│ │ ├── package.lisp
│ │ ├── section.lisp
│ │ ├── stem.lisp
│ │ ├── tests/
│ │ │ └── tests.lisp
│ │ ├── text-rank.lisp
│ │ └── tokenize.lisp
│ ├── download-manager/
│ │ ├── engine.lisp
│ │ ├── native.lisp
│ │ └── package.lisp
│ ├── nasdf/
│ │ ├── install.lisp
│ │ ├── log.lisp
│ │ ├── nasdf.asd
│ │ ├── nasdf.lisp
│ │ ├── package.lisp
│ │ ├── readme.org
│ │ ├── systems.lisp
│ │ └── tests.lisp
│ ├── password-manager/
│ │ ├── package.lisp
│ │ ├── password-keepassxc.lisp
│ │ ├── password-pass.lisp
│ │ ├── password-security.lisp
│ │ └── password.lisp
│ ├── text-buffer/
│ │ ├── package.lisp
│ │ └── text-buffer.lisp
│ ├── theme/
│ │ ├── README.org
│ │ ├── package.lisp
│ │ ├── tests/
│ │ │ └── tests.lisp
│ │ ├── theme.lisp
│ │ └── utilities.lisp
│ └── user-interface/
│ ├── package.lisp
│ └── user-interface.lisp
├── licenses/
│ ├── ASSET-LICENSE
│ ├── DejaVu Fonts License.txt
│ └── SOURCE-LICENSE
├── makefile
├── nyxt.asd
├── source/
│ ├── about.lisp
│ ├── browser.lisp
│ ├── buffer.lisp
│ ├── clipboard.lisp
│ ├── color.lisp
│ ├── command-commands.lisp
│ ├── command.lisp
│ ├── concurrency.lisp
│ ├── conditions.lisp
│ ├── configuration-commands.lisp
│ ├── configuration.lisp
│ ├── describe.lisp
│ ├── dom.lisp
│ ├── external-editor.lisp
│ ├── foreign-interface.lisp
│ ├── global.lisp
│ ├── help.lisp
│ ├── history.lisp
│ ├── input.lisp
│ ├── inspector.lisp
│ ├── keyscheme.lisp
│ ├── manual.lisp
│ ├── message.lisp
│ ├── mode/
│ │ ├── annotate.lisp
│ │ ├── autofill.lisp
│ │ ├── base.lisp
│ │ ├── blocker.lisp
│ │ ├── bookmark.lisp
│ │ ├── bookmarklets.lisp
│ │ ├── buffer-listing.lisp
│ │ ├── certificate-exception.lisp
│ │ ├── cruise-control.lisp
│ │ ├── document.lisp
│ │ ├── download.lisp
│ │ ├── emacs.lisp
│ │ ├── expedition.lisp
│ │ ├── file-manager.lisp
│ │ ├── force-https.lisp
│ │ ├── help.lisp
│ │ ├── hint-prompt-buffer.lisp
│ │ ├── hint.lisp
│ │ ├── history-migration.lisp
│ │ ├── history.lisp
│ │ ├── input-edit.lisp
│ │ ├── keyscheme.lisp
│ │ ├── macro-edit.lisp
│ │ ├── message.lisp
│ │ ├── no-image.lisp
│ │ ├── no-script.lisp
│ │ ├── no-sound.lisp
│ │ ├── no-webgl.lisp
│ │ ├── passthrough.lisp
│ │ ├── password.lisp
│ │ ├── process.lisp
│ │ ├── prompt-buffer.lisp
│ │ ├── proxy.lisp
│ │ ├── reading-line.lisp
│ │ ├── repeat.lisp
│ │ ├── search-buffer.lisp
│ │ ├── small-web.lisp
│ │ ├── spell-check.lisp
│ │ ├── style.lisp
│ │ ├── user-script.lisp
│ │ ├── vi.lisp
│ │ ├── visual.lisp
│ │ └── watch.lisp
│ ├── mode.lisp
│ ├── package.lisp
│ ├── parenscript-macro.lisp
│ ├── prompt-buffer.lisp
│ ├── recent-buffers.lisp
│ ├── renderer/
│ │ ├── electron.lisp
│ │ ├── gi-gtk.lisp
│ │ └── gtk.lisp
│ ├── renderer-script.lisp
│ ├── renderer.lisp
│ ├── search-engine.lisp
│ ├── spinneret-tags.lisp
│ ├── start.lisp
│ ├── status.lisp
│ ├── time.lisp
│ ├── tutorial.lisp
│ ├── types.lisp
│ ├── urls.lisp
│ ├── user-classes.lisp
│ ├── user-files.lisp
│ ├── user-interface.lisp
│ ├── utilities.lisp
│ └── window.lisp
└── tests/
├── benchmarks/
│ ├── package.lisp
│ └── prompter.lisp
├── define-configuration.lisp
├── mode/
│ ├── annotate.lisp
│ ├── autofill.lisp
│ ├── base.lisp
│ ├── blocker.lisp
│ ├── bookmark.lisp
│ ├── bookmarklets.lisp
│ ├── buffer-listing.lisp
│ ├── certificate-exception.lisp
│ ├── cruise-control.lisp
│ ├── document.lisp
│ ├── download.lisp
│ ├── emacs.lisp
│ ├── expedition.lisp
│ ├── file-manager.lisp
│ ├── force-https.lisp
│ ├── help.lisp
│ ├── hint-prompt-buffer.lisp
│ ├── hint.lisp
│ ├── history.lisp
│ ├── input-edit.lisp
│ ├── keyscheme.lisp
│ ├── macro-edit.lisp
│ ├── message.lisp
│ ├── no-image.lisp
│ ├── no-script.lisp
│ ├── no-sound.lisp
│ ├── no-webgl.lisp
│ ├── passthrough.lisp
│ ├── password.lisp
│ ├── process.lisp
│ ├── prompt-buffer.lisp
│ ├── proxy.lisp
│ ├── reading-line.lisp
│ ├── reduce-tracking.lisp
│ ├── repeat.lisp
│ ├── search-buffer.lisp
│ ├── small-web.lisp
│ ├── spell-check.lisp
│ ├── style.lisp
│ ├── user-script.lisp
│ ├── vi.lisp
│ ├── visual.lisp
│ └── watch.lisp
├── mode.lisp
├── package.lisp
├── prompt-buffer.lisp
├── renderer/
│ ├── custom-schemes.lisp
│ ├── package.lisp
│ ├── search-buffer.lisp
│ └── set-url.lisp
├── test-data/
│ ├── hint-mode-html-document.html
│ └── history.lisp
├── urls.lisp
└── user-script-parsing.lisp
Condensed preview — 205 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,271K chars).
[
{
"path": ".dir-locals.el",
"chars": 2292,
"preview": "((nil . ((fill-column . 80)\n (project-vc-ignores . (\"./_build\"))\n (require-final-newline . t)\n ("
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 298,
"preview": "---\nname: Bug report\nabout: Bug report\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\n\n**Steps to reprodu"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 289,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: feature\nassignees: ''\n\n---\n\n**Is you"
},
{
"path": ".github/ISSUE_TEMPLATE/ui_request.md",
"chars": 282,
"preview": "---\nname: UI request\nabout: Suggest a UI change for this project\ntitle: ''\nlabels: ui/ux\nassignees: ''\n\n---\n\n**Please de"
},
{
"path": ".github/SECURITY.md",
"chars": 171,
"preview": "# Security Policy\n\n## Supported Versions\n\nOnly the latest stable version is currently supported with security updates.\n\n"
},
{
"path": ".github/pull_request_template.md",
"chars": 384,
"preview": "# Description\n\n- Please include a summary of the change.\n\nFixes # (issue)\n\n# Checklist:\n\n- [ ] Git branch state is merga"
},
{
"path": ".gitignore",
"chars": 424,
"preview": "# Ignore build artifacts\nnyxt\nbuild/\nnode_modules/\npackage.json\npackage-lock.json\n\n# Ignore compiled lisp files\n*.FASL\n*"
},
{
"path": ".gitmodules",
"chars": 13615,
"preview": "[submodule \"_build/alexandria\"]\n\tpath = _build/alexandria\n\turl = https://gitlab.common-lisp.net/alexandria/alexandria.gi"
},
{
"path": "INSTALL",
"chars": 1039,
"preview": "Usage:\n\n make all # Generate Nyxt binary at $PWD.\n make install # Install Nyxt.\n ma"
},
{
"path": "README.org",
"chars": 2735,
"preview": "* Nyxt browser\n#+html: <img src=\"https://nyxt-browser.com/static/image/nyxt_256x256.png\" align=\"right\"/>\n\n*Nyxt* [nýkst]"
},
{
"path": "_build/README.org",
"chars": 393,
"preview": "This directory contains all Common Lisp dependencies. They are fetched via Git\nsubmodules. This gives us good reproduc"
},
{
"path": "assets/Info.plist",
"chars": 1755,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "assets/nyxt.appimage.desktop",
"chars": 531,
"preview": "[Desktop Entry]\nName=Nyxt\nComment=Web Browser for Hackers\nGenericName=Web Browser\nKeywords=Internet;WWW;Browser;Web;Expl"
},
{
"path": "assets/nyxt.desktop",
"chars": 534,
"preview": "[Desktop Entry]\nName=Nyxt\nComment=Web Browser for Hackers\nGenericName=Web Browser\nKeywords=Internet;WWW;Browser;Web;Expl"
},
{
"path": "assets/nyxt.metainfo.xml",
"chars": 4663,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<component type=\"desktop-application\">\n <!-- Spec: https://www.freedesktop.org/s"
},
{
"path": "developer-manual.org",
"chars": 10470,
"preview": "#+TITLE: Nyxt Developer's Manual\n\n# Install org-make-toc so the TOC below will be automatically generated.\n# https://git"
},
{
"path": "libraries/analysis/README.org",
"chars": 1293,
"preview": "* Analysis\nAnalysis is a library that provides facilities to help analyze and\nunderstand data. Listed below are the clas"
},
{
"path": "libraries/analysis/analysis.lisp",
"chars": 4594,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :analysis)\n\n(exp"
},
{
"path": "libraries/analysis/composite-sequence.lisp",
"chars": 3206,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;; Given the following seque"
},
{
"path": "libraries/analysis/data.lisp",
"chars": 5819,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :analysis)\n\n(def"
},
{
"path": "libraries/analysis/dbscan.lisp",
"chars": 4203,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :analysis)\n\n;;; "
},
{
"path": "libraries/analysis/document-vector.lisp",
"chars": 2751,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :analysis)\n\n;;; "
},
{
"path": "libraries/analysis/package.lisp",
"chars": 180,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :analys"
},
{
"path": "libraries/analysis/section.lisp",
"chars": 1386,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :analysis)\n\n;;; "
},
{
"path": "libraries/analysis/stem.lisp",
"chars": 13995,
"preview": "(in-package :analysis)\n\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n;; The software is completely free "
},
{
"path": "libraries/analysis/tests/tests.lisp",
"chars": 4249,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :analys"
},
{
"path": "libraries/analysis/text-rank.lisp",
"chars": 5633,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :analysis)\n\n;;; "
},
{
"path": "libraries/analysis/tokenize.lisp",
"chars": 1245,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :analysis)\n\n(def"
},
{
"path": "libraries/download-manager/engine.lisp",
"chars": 6441,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :download-manage"
},
{
"path": "libraries/download-manager/native.lisp",
"chars": 4908,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;;; Native Common Lisp downl"
},
{
"path": "libraries/download-manager/package.lisp",
"chars": 537,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :downlo"
},
{
"path": "libraries/nasdf/install.lisp",
"chars": 13434,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nasdf)\n\n(export"
},
{
"path": "libraries/nasdf/log.lisp",
"chars": 540,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nasdf)\n\n;; TODO"
},
{
"path": "libraries/nasdf/nasdf.asd",
"chars": 453,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(defsystem \"nasdf\"\n :versio"
},
{
"path": "libraries/nasdf/nasdf.lisp",
"chars": 582,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nasdf)\n\n(defmac"
},
{
"path": "libraries/nasdf/package.lisp",
"chars": 1456,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n#+sb-package-locks\n(eval-whe"
},
{
"path": "libraries/nasdf/readme.org",
"chars": 838,
"preview": "#+TITLE: NASDF\n\nNASDF is an ASDF extension providing utilities to ease system setup, testing and\ninstallation.\n\n* Featur"
},
{
"path": "libraries/nasdf/systems.lisp",
"chars": 6397,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nasdf)\n\n(export"
},
{
"path": "libraries/nasdf/tests.lisp",
"chars": 2333,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nasdf)\n\n(export"
},
{
"path": "libraries/password-manager/package.lisp",
"chars": 368,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :passwo"
},
{
"path": "libraries/password-manager/password-keepassxc.lisp",
"chars": 4452,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :password)\n\n(def"
},
{
"path": "libraries/password-manager/password-pass.lisp",
"chars": 4639,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :password)\n\n(def"
},
{
"path": "libraries/password-manager/password-security.lisp",
"chars": 1357,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :password)\n\n;;; "
},
{
"path": "libraries/password-manager/password.lisp",
"chars": 3192,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :password)\n\n(def"
},
{
"path": "libraries/text-buffer/package.lisp",
"chars": 175,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :text-b"
},
{
"path": "libraries/text-buffer/text-buffer.lisp",
"chars": 6571,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :text-buffer)\n\n("
},
{
"path": "libraries/theme/README.org",
"chars": 7432,
"preview": "#+TITLE: Theme library for Nyxt\n#+PROPERTY: :results silent\n\n* Overview\n\nThis general purpose theme library provides the"
},
{
"path": "libraries/theme/package.lisp",
"chars": 241,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :theme\n"
},
{
"path": "libraries/theme/tests/tests.lisp",
"chars": 3108,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package cl-user)\n(uiop:d"
},
{
"path": "libraries/theme/theme.lisp",
"chars": 8101,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :theme)\n\n(define"
},
{
"path": "libraries/theme/utilities.lisp",
"chars": 1724,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :theme)\n\n(serape"
},
{
"path": "libraries/user-interface/package.lisp",
"chars": 143,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :user-i"
},
{
"path": "libraries/user-interface/user-interface.lisp",
"chars": 3325,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :user-interface)"
},
{
"path": "licenses/ASSET-LICENSE",
"chars": 1226,
"preview": "All non-source code assets are licensed as CC BY-SA.\n\nCreative Commons Deed\nThis is a human-readable summary of the full"
},
{
"path": "licenses/DejaVu Fonts License.txt",
"chars": 4765,
"preview": "Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.\nGlyphs imported from Arev fonts are (c) Tavmjo"
},
{
"path": "licenses/SOURCE-LICENSE",
"chars": 1524,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2017-2025, Atlas Engineer LLC.\nAll rights reserved.\n\nRedistribution and use in sourc"
},
{
"path": "makefile",
"chars": 1966,
"preview": "# SPDX-FileCopyrightText: Atlas Engineer LLC\n# SPDX-License-Identifier: BSD-3-Clause\n\n## Use Bourne shell syntax.\nSHELL "
},
{
"path": "nyxt.asd",
"chars": 16201,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n#-asdf3.1 (error \"Nyxt requi"
},
{
"path": "source/about.lisp",
"chars": 3602,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/browser.lisp",
"chars": 25409,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(hooks:d"
},
{
"path": "source/buffer.lisp",
"chars": 48131,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(hooks:d"
},
{
"path": "source/clipboard.lisp",
"chars": 1106,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(-> ring"
},
{
"path": "source/color.lisp",
"chars": 4483,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defvar "
},
{
"path": "source/command-commands.lisp",
"chars": 7210,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/command.lisp",
"chars": 14434,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defvar "
},
{
"path": "source/concurrency.lisp",
"chars": 5540,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defun i"
},
{
"path": "source/conditions.lisp",
"chars": 797,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(export-"
},
{
"path": "source/configuration-commands.lisp",
"chars": 5707,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defun e"
},
{
"path": "source/configuration.lisp",
"chars": 26691,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/describe.lisp",
"chars": 30670,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defun d"
},
{
"path": "source/dom.lisp",
"chars": 18288,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/d"
},
{
"path": "source/external-editor.lisp",
"chars": 2708,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defun %"
},
{
"path": "source/foreign-interface.lisp",
"chars": 24392,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defmacr"
},
{
"path": "source/global.lisp",
"chars": 4284,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n;; Packag"
},
{
"path": "source/help.lisp",
"chars": 19989,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n;; Moved"
},
{
"path": "source/history.lisp",
"chars": 2654,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/input.lisp",
"chars": 6573,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(-> bind"
},
{
"path": "source/inspector.lisp",
"chars": 10620,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defvar "
},
{
"path": "source/keyscheme.lisp",
"chars": 2034,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;; `uiop:define-package' ins"
},
{
"path": "source/manual.lisp",
"chars": 37836,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defun m"
},
{
"path": "source/message.lisp",
"chars": 6527,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/mode/annotate.lisp",
"chars": 8061,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/autofill.lisp",
"chars": 3919,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/base.lisp",
"chars": 4557,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/mode/blocker.lisp",
"chars": 6239,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/bookmark.lisp",
"chars": 20186,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/bookmarklets.lisp",
"chars": 13720,
"preview": ";;;; This package and file serves as a source for bookmarklets that\n;;;; originate outside of the Nyxt codebase. Eventua"
},
{
"path": "source/mode/buffer-listing.lisp",
"chars": 1664,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/certificate-exception.lisp",
"chars": 2540,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/cruise-control.lisp",
"chars": 2106,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/document.lisp",
"chars": 25871,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/download.lisp",
"chars": 8488,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/emacs.lisp",
"chars": 599,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/expedition.lisp",
"chars": 2340,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/file-manager.lisp",
"chars": 14694,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/force-https.lisp",
"chars": 3049,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/help.lisp",
"chars": 906,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/hint-prompt-buffer.lisp",
"chars": 910,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/hint.lisp",
"chars": 23144,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/history-migration.lisp",
"chars": 7011,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/history.lisp",
"chars": 2712,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/input-edit.lisp",
"chars": 7108,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/keyscheme.lisp",
"chars": 1637,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/macro-edit.lisp",
"chars": 5774,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/message.lisp",
"chars": 1029,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/no-image.lisp",
"chars": 650,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/no-script.lisp",
"chars": 1125,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/no-sound.lisp",
"chars": 669,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/no-webgl.lisp",
"chars": 943,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/passthrough.lisp",
"chars": 964,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/password.lisp",
"chars": 9761,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/process.lisp",
"chars": 3758,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/prompt-buffer.lisp",
"chars": 23542,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/proxy.lisp",
"chars": 1988,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/reading-line.lisp",
"chars": 3600,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/repeat.lisp",
"chars": 6738,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/search-buffer.lisp",
"chars": 24785,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/small-web.lisp",
"chars": 14414,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/spell-check.lisp",
"chars": 3827,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/style.lisp",
"chars": 1635,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/user-script.lisp",
"chars": 10950,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/vi.lisp",
"chars": 5420,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/visual.lisp",
"chars": 10866,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode/watch.lisp",
"chars": 2203,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/m"
},
{
"path": "source/mode.lisp",
"chars": 24560,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defclas"
},
{
"path": "source/package.lisp",
"chars": 5552,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;; Some compilers (e.g. SBCL"
},
{
"path": "source/parenscript-macro.lisp",
"chars": 9666,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;; `uiop:define-package' ins"
},
{
"path": "source/prompt-buffer.lisp",
"chars": 29699,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(eval-al"
},
{
"path": "source/recent-buffers.lisp",
"chars": 1749,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defmeth"
},
{
"path": "source/renderer/electron.lisp",
"chars": 26631,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/r"
},
{
"path": "source/renderer/gi-gtk.lisp",
"chars": 2844,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;;;; This file produces two "
},
{
"path": "source/renderer/gtk.lisp",
"chars": 92592,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/r"
},
{
"path": "source/renderer-script.lisp",
"chars": 17664,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(export-"
},
{
"path": "source/renderer.lisp",
"chars": 606,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/search-engine.lisp",
"chars": 4086,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/spinneret-tags.lisp",
"chars": 32499,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :spinneret)\n\n(de"
},
{
"path": "source/start.lisp",
"chars": 18108,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/status.lisp",
"chars": 17065,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/time.lisp",
"chars": 293,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defun s"
},
{
"path": "source/tutorial.lisp",
"chars": 26676,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defun t"
},
{
"path": "source/types.lisp",
"chars": 2172,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;; `uiop:define-package' ins"
},
{
"path": "source/urls.lisp",
"chars": 18766,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(export-"
},
{
"path": "source/user-classes.lisp",
"chars": 6700,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(defclas"
},
{
"path": "source/user-files.lisp",
"chars": 5955,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(define-"
},
{
"path": "source/user-interface.lisp",
"chars": 1526,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n;;;; Implementations of upda"
},
{
"path": "source/utilities.lisp",
"chars": 6557,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(uiop:define-package :nyxt/u"
},
{
"path": "source/window.lisp",
"chars": 6409,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt)\n\n(hooks:d"
},
{
"path": "tests/benchmarks/package.lisp",
"chars": 309,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(benchmark:define-benchmark-"
},
{
"path": "tests/benchmarks/prompter.lisp",
"chars": 1149,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package nyxt/benchmarks)"
},
{
"path": "tests/define-configuration.lisp",
"chars": 2671,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/annotate.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/autofill.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/base.lisp",
"chars": 355,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/blocker.lisp",
"chars": 390,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/bookmark.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/bookmarklets.lisp",
"chars": 415,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/buffer-listing.lisp",
"chars": 425,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/certificate-exception.lisp",
"chars": 541,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/cruise-control.lisp",
"chars": 425,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/document.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/download.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/emacs.lisp",
"chars": 641,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/expedition.lisp",
"chars": 405,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/file-manager.lisp",
"chars": 415,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/force-https.lisp",
"chars": 422,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/help.lisp",
"chars": 375,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/hint-prompt-buffer.lisp",
"chars": 514,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/hint.lisp",
"chars": 375,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/history.lisp",
"chars": 390,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/input-edit.lisp",
"chars": 405,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/keyscheme.lisp",
"chars": 410,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/macro-edit.lisp",
"chars": 405,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/message.lisp",
"chars": 390,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/no-image.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/no-script.lisp",
"chars": 400,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/no-sound.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/no-webgl.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/passthrough.lisp",
"chars": 420,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/password.lisp",
"chars": 395,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/process.lisp",
"chars": 4148,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n(us"
},
{
"path": "tests/mode/prompt-buffer.lisp",
"chars": 420,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/proxy.lisp",
"chars": 380,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/reading-line.lisp",
"chars": 415,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/reduce-tracking.lisp",
"chars": 1243,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n;;"
},
{
"path": "tests/mode/repeat.lisp",
"chars": 510,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n;;"
},
{
"path": "tests/mode/search-buffer.lisp",
"chars": 6081,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/small-web.lisp",
"chars": 400,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/spell-check.lisp",
"chars": 410,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/style.lisp",
"chars": 380,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/user-script.lisp",
"chars": 410,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/vi.lisp",
"chars": 981,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode/visual.lisp",
"chars": 490,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n;;"
},
{
"path": "tests/mode/watch.lisp",
"chars": 380,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/mode.lisp",
"chars": 925,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(d"
},
{
"path": "tests/package.lisp",
"chars": 580,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/t"
},
{
"path": "tests/prompt-buffer.lisp",
"chars": 2378,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests)\n\n(u"
},
{
"path": "tests/renderer/custom-schemes.lisp",
"chars": 2439,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests/rend"
},
{
"path": "tests/renderer/package.lisp",
"chars": 1531,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(nyxt:define-package :nyxt/t"
},
{
"path": "tests/renderer/search-buffer.lisp",
"chars": 3821,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests/rend"
},
{
"path": "tests/renderer/set-url.lisp",
"chars": 330,
"preview": ";;;; SPDX-FileCopyrightText: Atlas Engineer LLC\n;;;; SPDX-License-Identifier: BSD-3-Clause\n\n(in-package :nyxt/tests/rend"
}
]
// ... and 5 more files (download for full content)
About this extraction
This page contains the full source code of the atlas-engineer/nyxt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 205 files (1.2 MB), approximately 293.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.